Repository: genjidb/genji
Pull request #203 (2020-09-26 08:50:48)

Use third party json parser
This PR removes the dependency to `encoding/json` and uses `jsonparser` to parse json instead. It also adds some fixes to improve the compatibility with Genji.js and wasm.
It also brings back the previous codec as an option,


Patch:
diff --git a/cmd/genji/go.mod b/cmd/genji/go.mod
index c3c5c408..4fbab18f 100644
--- a/cmd/genji/go.mod
+++ b/cmd/genji/go.mod
@@ -4,13 +4,10 @@ go 1.15
 
 require (
 	github.com/agnivade/levenshtein v1.1.0
-	github.com/c-bata/go-prompt v0.2.3
-	github.com/dgraph-io/badger/v2 v2.2007.1
+	github.com/c-bata/go-prompt v0.2.5
+	github.com/dgraph-io/badger/v2 v2.2007.2
 	github.com/genjidb/genji v0.8.0
 	github.com/genjidb/genji/engine/badgerengine v0.8.0
-	github.com/mattn/go-runewidth v0.0.9 // indirect
-	github.com/mattn/go-tty v0.0.3 // indirect
-	github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect
 	github.com/stretchr/testify v1.6.1
 	github.com/urfave/cli/v2 v2.2.0
 )
diff --git a/cmd/genji/go.sum b/cmd/genji/go.sum
index df231a8f..0a9c58a1 100644
--- a/cmd/genji/go.sum
+++ b/cmd/genji/go.sum
@@ -1,15 +1,16 @@
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
 github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
-github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
 github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
 github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/c-bata/go-prompt v0.2.3 h1:jjCS+QhG/sULBhAaBdjb2PlMRVaKXQgn+4yzaauvs2s=
-github.com/c-bata/go-prompt v0.2.3/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
+github.com/buger/jsonparser v1.0.0 h1:etJTGF5ESxjI0Ic2UaLQs2LQQpa8G9ykQScukbh4L8A=
+github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
+github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y=
+github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@@ -22,11 +23,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgraph-io/badger/v2 v2.0.3 h1:inzdf6VF/NZ+tJ8RwwYMjJMvsOALTHYdozn0qSl6XJI=
 github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
-github.com/dgraph-io/badger/v2 v2.2007.1 h1:t36VcBCpo4SsmAD5M8wVv1ieVzcALyGfaJ92z4ccULM=
-github.com/dgraph-io/badger/v2 v2.2007.1/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
-github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU=
+github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
+github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
 github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
 github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
@@ -47,31 +46,26 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
 github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
-github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
 github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 h1:pd4YKIqCB0U7O2I4gWHgEUA2mCEOENmco0l/bM957bU=
-github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03/go.mod h1:Z9+Ul5bCbBKnbCvdOWbLqTHhJiYV414CURZJba6L8qA=
+github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
+github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
@@ -81,7 +75,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
@@ -91,9 +84,7 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -111,7 +102,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -119,23 +109,22 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
+golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/cmd/genji/shell/shell.go b/cmd/genji/shell/shell.go
index 9cf91d09..8dc92de6 100644
--- a/cmd/genji/shell/shell.go
+++ b/cmd/genji/shell/shell.go
@@ -2,6 +2,7 @@ package shell
 
 import (
 	"bufio"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -279,7 +280,13 @@ func (sh *Shell) runQuery(q string) error {
 	}
 
 	defer res.Close()
-	return document.IteratorToJSON(os.Stdout, res)
+
+	enc := json.NewEncoder(os.Stdout)
+	enc.SetEscapeHTML(false)
+	enc.SetIndent("", "  ")
+	return res.Iterate(func(d document.Document) error {
+		return enc.Encode(d)
+	})
 }
 
 func (sh *Shell) exit() {
diff --git a/database/database.go b/database/database.go
index 461c98be..919fa31f 100644
--- a/database/database.go
+++ b/database/database.go
@@ -7,7 +7,6 @@ import (
 	"sync/atomic"
 
 	"github.com/genjidb/genji/document/encoding"
-	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine"
 )
 
@@ -34,11 +33,19 @@ type Database struct {
 	Codec encoding.Codec
 }
 
+type Options struct {
+	Codec encoding.Codec
+}
+
 // New initializes the DB using the given engine.
-func New(ng engine.Engine) (*Database, error) {
+func New(ng engine.Engine, opts Options) (*Database, error) {
+	if opts.Codec == nil {
+		return nil, errors.New("missing codec")
+	}
+
 	db := Database{
 		ng:    ng,
-		Codec: msgpack.NewCodec(),
+		Codec: opts.Codec,
 	}
 
 	ntx, err := db.ng.Begin(true)
diff --git a/db.go b/db.go
index 631a98b5..31eb0b4b 100644
--- a/db.go
+++ b/db.go
@@ -3,7 +3,6 @@ package genji
 import (
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
-	"github.com/genjidb/genji/engine"
 	"github.com/genjidb/genji/sql/parser"
 	"github.com/genjidb/genji/sql/query"
 )
@@ -13,18 +12,6 @@ type DB struct {
 	DB *database.Database
 }
 
-// New initializes the DB using the given engine.
-func New(ng engine.Engine) (*DB, error) {
-	db, err := database.New(ng)
-	if err != nil {
-		return nil, err
-	}
-
-	return &DB{
-		DB: db,
-	}, nil
-}
-
 // Close the database.
 func (db *DB) Close() error {
 	return db.DB.Close()
diff --git a/document/array.go b/document/array.go
index eb744774..58bf0683 100644
--- a/document/array.go
+++ b/document/array.go
@@ -1,8 +1,11 @@
 package document
 
 import (
+	"bytes"
 	"errors"
 	"sort"
+
+	"github.com/buger/jsonparser"
 )
 
 // ErrValueNotFound must be returned by Array implementations, when calling the GetByIndex method and
@@ -158,6 +161,33 @@ func (vb *ValueBuffer) Replace(index int, v Value) error {
 	return nil
 }
 
+// MarshalJSON implements the json.Marshaler interface.
+func (vb *ValueBuffer) MarshalJSON() ([]byte, error) {
+	return jsonArray{Array: vb}.MarshalJSON()
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (vb *ValueBuffer) UnmarshalJSON(data []byte) error {
+	var err error
+	_, perr := jsonparser.ArrayEach(data, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
+		var v Value
+		v, err = parseJSONValue(dataType, value)
+		if err != nil {
+			return
+		}
+
+		*vb = vb.Append(v)
+	})
+	if err != nil {
+		return err
+	}
+	if perr != nil {
+		return perr
+	}
+
+	return nil
+}
+
 type sortableArray struct {
 	vb  ValueBuffer
 	err error
@@ -231,3 +261,33 @@ func SortArray(a Array) (Array, error) {
 
 	return &s.vb, nil
 }
+
+type jsonArray struct {
+	Array
+}
+
+func (j jsonArray) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+
+	buf.WriteByte('[')
+	var notFirst bool
+	err := j.Array.Iterate(func(i int, v Value) error {
+		if notFirst {
+			buf.WriteString(", ")
+		}
+		notFirst = true
+
+		data, err := v.MarshalJSON()
+		if err != nil {
+			return err
+		}
+
+		_, err = buf.Write(data)
+		return err
+	})
+	if err != nil {
+		return nil, err
+	}
+	buf.WriteByte(']')
+	return buf.Bytes(), nil
+}
diff --git a/document/create.go b/document/create.go
index 7312aa01..aa9ae277 100644
--- a/document/create.go
+++ b/document/create.go
@@ -11,6 +11,16 @@ import (
 	"time"
 )
 
+// NewFromJSON creates a document from a JSON object.
+func NewFromJSON(data []byte) (Document, error) {
+	var fb FieldBuffer
+	err := fb.UnmarshalJSON(data)
+	if err != nil {
+		return nil, err
+	}
+	return &fb, nil
+}
+
 // NewFromMap creates a document from a map.
 // Due to the way maps are designed, iteration order is not guaranteed.
 func NewFromMap(m interface{}) (Document, error) {
@@ -52,6 +62,11 @@ func (m mapDocument) GetByField(field string) (Value, error) {
 	return NewValue(v.Interface())
 }
 
+// MarshalJSON implements the json.Marshaler interface.
+func (m mapDocument) MarshalJSON() ([]byte, error) {
+	return jsonDocument{Document: m}.MarshalJSON()
+}
+
 // NewFromStruct creates a document from a struct using reflection.
 func NewFromStruct(s interface{}) (Document, error) {
 	ref := reflect.Indirect(reflect.ValueOf(s))
@@ -141,6 +156,11 @@ func (s structDocument) GetByField(field string) (Value, error) {
 	return NewValue(v.Interface())
 }
 
+// MarshalJSON implements the json.Marshaler interface.
+func (s structDocument) MarshalJSON() ([]byte, error) {
+	return jsonDocument{Document: s}.MarshalJSON()
+}
+
 // NewValue creates a value whose type is infered from x.
 func NewValue(x interface{}) (Value, error) {
 	// Attempt exact matches first:
diff --git a/document/document.go b/document/document.go
index 2c22dba0..648a32d4 100644
--- a/document/document.go
+++ b/document/document.go
@@ -2,10 +2,13 @@
 package document
 
 import (
+	"bytes"
 	"errors"
 	"sort"
 	"strconv"
 	"strings"
+
+	"github.com/buger/jsonparser"
 )
 
 // ErrFieldNotFound must be returned by Document implementations, when calling the GetByField method and
@@ -22,6 +25,16 @@ type Document interface {
 	GetByField(field string) (Value, error)
 }
 
+// MarshalJSON encodes a document to json.
+func MarshalJSON(d Document) ([]byte, error) {
+	return jsonDocument{d}.MarshalJSON()
+}
+
+// MarshalJSONArray encodes an array to json.
+func MarshalJSONArray(a Array) ([]byte, error) {
+	return jsonArray{a}.MarshalJSON()
+}
+
 // A Keyer returns the key identifying documents in their storage.
 // This is usually implemented by documents read from storages.
 type Keyer interface {
@@ -73,6 +86,24 @@ func NewFieldBuffer() *FieldBuffer {
 	return new(FieldBuffer)
 }
 
+// MarshalJSON implements the json.Marshaler interface.
+func (fb *FieldBuffer) MarshalJSON() ([]byte, error) {
+	return jsonDocument{Document: fb}.MarshalJSON()
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (fb *FieldBuffer) UnmarshalJSON(data []byte) error {
+	return jsonparser.ObjectEach(data, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
+		v, err := parseJSONValue(dataType, value)
+		if err != nil {
+			return err
+		}
+
+		fb.Add(string(key), v)
+		return nil
+	})
+}
+
 type fieldValue struct {
 	Field string
 	Value Value
@@ -395,3 +426,38 @@ func (p ValuePath) getValueFromValue(v Value) (Value, error) {
 
 	return Value{}, ErrFieldNotFound
 }
+
+type jsonDocument struct {
+	Document
+}
+
+func (j jsonDocument) MarshalJSON() ([]byte, error) {
+	var buf bytes.Buffer
+
+	buf.WriteByte('{')
+
+	var notFirst bool
+	err := j.Document.Iterate(func(f string, v Value) error {
+		if notFirst {
+			buf.WriteString(", ")
+		}
+		notFirst = true
+
+		buf.WriteString(strconv.Quote(f))
+		buf.WriteString(": ")
+
+		data, err := v.MarshalJSON()
+		if err != nil {
+			return err
+		}
+		_, err = buf.Write(data)
+		return err
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	buf.WriteByte('}')
+
+	return buf.Bytes(), nil
+}
diff --git a/document/encoding/custom/codec.go b/document/encoding/custom/codec.go
new file mode 100644
index 00000000..5b16fd33
--- /dev/null
+++ b/document/encoding/custom/codec.go
@@ -0,0 +1,342 @@
+package custom
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"io"
+	"time"
+
+	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/key"
+)
+
+// A Codec is a custom implementation of an encoding.Codec.
+type Codec struct{}
+
+// NewCodec creates a custom codec.
+func NewCodec() Codec {
+	return Codec{}
+}
+
+// NewEncoder implements the encoding.Codec interface.
+func (c Codec) NewEncoder(w io.Writer) encoding.Encoder {
+	return NewEncoder(w)
+}
+
+// NewDocument implements the encoding.Codec interface.
+func (c Codec) NewDocument(data []byte) document.Document {
+	return EncodedDocument(data)
+}
+
+// Encoder encodes Genji documents and values
+// in MessagePack.
+type Encoder struct {
+	w io.Writer
+}
+
+// NewEncoder creates an Encoder that writes in the given writer.
+func NewEncoder(w io.Writer) *Encoder {
+	return &Encoder{
+		w: w,
+	}
+}
+
+// EncodeDocument encodes d.
+func (e *Encoder) EncodeDocument(d document.Document) error {
+	data, err := EncodeDocument(d)
+	if err != nil {
+		return err
+	}
+
+	_, err = e.w.Write(data)
+	return err
+}
+
+// EncodeDocument takes a document and encodes it using the encoding.Format type.
+func EncodeDocument(d document.Document) ([]byte, error) {
+	if ec, ok := d.(EncodedDocument); ok {
+		return ec, nil
+	}
+
+	var format Format
+
+	var offset uint64
+	var dataList [][]byte
+
+	err := d.Iterate(func(f string, v document.Value) error {
+		data, err := EncodeValue(v)
+		if err != nil {
+			return err
+		}
+
+		format.Header.FieldHeaders = append(format.Header.FieldHeaders, FieldHeader{
+			NameSize:   uint64(len(f)),
+			NameString: f,
+			Type:       uint64(v.Type),
+			Size:       uint64(len(data)),
+			Offset:     offset,
+		})
+
+		offset += uint64(len(data))
+		dataList = append(dataList, data)
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	var buf bytes.Buffer
+	_, err = format.Header.WriteTo(&buf)
+	if err != nil {
+		return nil, err
+	}
+
+	buf.Grow(format.Header.BodySize())
+
+	for _, data := range dataList {
+		_, err = buf.Write(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return buf.Bytes(), nil
+}
+
+// DecodeDocument takes a byte slice and returns a lazily decoded document.
+// If buf is malformed, an error will be returned when calling one of the document method.
+func DecodeDocument(buf []byte) document.Document {
+	return EncodedDocument(buf)
+}
+
+// EncodeValue encodes any value to a binary representation.
+func EncodeValue(v document.Value) ([]byte, error) {
+	switch v.Type {
+	case document.DocumentValue:
+		return EncodeDocument(v.V.(document.Document))
+	case document.ArrayValue:
+		return EncodeArray(v.V.(document.Array))
+	case document.BlobValue:
+		return v.V.([]byte), nil
+	case document.TextValue:
+		return []byte(v.V.(string)), nil
+	case document.BoolValue:
+		return key.AppendBool(nil, v.V.(bool)), nil
+	case document.IntegerValue:
+		return encodeInt64(v.V.(int64)), nil
+	case document.DoubleValue:
+		key.AppendFloat64(nil, v.V.(float64))
+	case document.DurationValue:
+		return encodeInt64(int64(v.V.(time.Duration))), nil
+	case document.NullValue:
+		return nil, nil
+	}
+
+	return nil, errors.New("unknown type")
+}
+
+func encodeInt64(x int64) []byte {
+	buf := make([]byte, binary.MaxVarintLen64)
+	n := binary.PutVarint(buf, x)
+	return buf[:n]
+}
+
+// An EncodedDocument implements the document.Document interface on top of an encoded representation of a
+// document.
+// It is useful to avoid decoding the entire document when only a few fields are needed.
+type EncodedDocument []byte
+
+// GetByField decodes the selected field.
+func (e EncodedDocument) GetByField(field string) (document.Value, error) {
+	return decodeValueFromDocument(e, field)
+}
+
+// Iterate decodes each fields one by one and passes them to fn until the end of the document
+// or until fn returns an error.
+func (e EncodedDocument) Iterate(fn func(field string, value document.Value) error) error {
+	var format Format
+	err := format.Decode(e)
+	if err != nil {
+		return err
+	}
+
+	for _, fh := range format.Header.FieldHeaders {
+		v, err := DecodeValue(document.ValueType(fh.Type), format.Body[fh.Offset:fh.Offset+fh.Size])
+		if err != nil {
+			return err
+		}
+
+		err = fn(string(fh.Name), v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (e EncodedDocument) MarshalJSON() ([]byte, error) {
+	return document.MarshalJSON(e)
+}
+
+// An EncodedArray implements the document.Array interface on top of an encoded representation of an
+// array.
+// It is useful to avoid decoding the entire array when only a few values are needed.
+type EncodedArray []byte
+
+// Iterate goes through all the values of the array and calls the given function by passing each one of them.
+// If the given function returns an error, the iteration stops.
+func (e EncodedArray) Iterate(fn func(i int, value document.Value) error) error {
+	var format Format
+	err := format.Decode(e)
+	if err != nil {
+		return err
+	}
+
+	for _, fh := range format.Header.FieldHeaders {
+		v, err := DecodeValue(document.ValueType(fh.Type), format.Body[fh.Offset:fh.Offset+fh.Size])
+		if err != nil {
+			return err
+		}
+
+		i, _ := binary.Varint(fh.Name)
+		err = fn(int(i), v)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// GetByIndex returns a value by index of the array.
+func (e EncodedArray) GetByIndex(i int) (document.Value, error) {
+	return decodeValueFromDocument(e, string(encodeInt64(int64(i))))
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+func (e EncodedArray) MarshalJSON() ([]byte, error) {
+	return document.MarshalJSONArray(e)
+}
+
+func decodeValueFromDocument(data []byte, field string) (document.Value, error) {
+	hsize, n := binary.Uvarint(data)
+	if n <= 0 {
+		return document.Value{}, errors.New("cannot decode data")
+	}
+
+	hdata := data[n : n+int(hsize)]
+	body := data[n+len(hdata):]
+
+	// skip number of fields
+	_, n = binary.Uvarint(hdata)
+	if n <= 0 {
+		return document.Value{}, errors.New("cannot decode data")
+	}
+	hdata = hdata[n:]
+
+	var fh FieldHeader
+	for len(hdata) > 0 {
+		n, err := fh.Decode(hdata)
+		if err != nil {
+			return document.Value{}, err
+		}
+		hdata = hdata[n:]
+
+		if field == string(fh.Name) {
+			return DecodeValue(document.ValueType(fh.Type), body[fh.Offset:fh.Offset+fh.Size])
+		}
+	}
+
+	return document.Value{}, document.ErrValueNotFound
+}
+
+// EncodeArray encodes a into its binary representation.
+func EncodeArray(a document.Array) ([]byte, error) {
+	var format Format
+
+	var offset uint64
+	var dataList [][]byte
+
+	err := a.Iterate(func(i int, v document.Value) error {
+		data, err := EncodeValue(v)
+		if err != nil {
+			return err
+		}
+
+		index := encodeInt64(int64(i))
+
+		format.Header.FieldHeaders = append(format.Header.FieldHeaders, FieldHeader{
+			NameSize: uint64(len(index)),
+			Name:     index,
+			Type:     uint64(v.Type),
+			Size:     uint64(len(data)),
+			Offset:   offset,
+		})
+
+		offset += uint64(len(data))
+		dataList = append(dataList, data)
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	var buf bytes.Buffer
+	_, err = format.Header.WriteTo(&buf)
+	if err != nil {
+		return nil, err
+	}
+
+	buf.Grow(format.Header.BodySize())
+
+	for _, data := range dataList {
+		_, err = buf.Write(data)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return buf.Bytes(), nil
+}
+
+// DecodeArray takes a byte slice and returns a lazily decoded array.
+// If buf is malformed, an error will be returned when calling one of the array method.
+func DecodeArray(buf []byte) document.Array {
+	return EncodedArray(buf)
+}
+
+// DecodeValue takes some encoded data and decodes it to the target type t.
+func DecodeValue(t document.ValueType, data []byte) (document.Value, error) {
+	switch t {
+	case document.DocumentValue:
+		return document.NewDocumentValue(EncodedDocument(data)), nil
+	case document.ArrayValue:
+		return document.NewArrayValue(EncodedArray(data)), nil
+	case document.BlobValue:
+		return document.NewBlobValue(data), nil
+	case document.TextValue:
+		return document.NewTextValue(string(data)), nil
+	case document.BoolValue:
+		return document.NewBoolValue(key.DecodeBool(data)), nil
+	case document.IntegerValue:
+		x, _ := binary.Varint(data)
+		return document.NewIntegerValue(x), nil
+	case document.DoubleValue:
+		x, err := key.DecodeFloat64(data)
+		if err != nil {
+			return document.Value{}, err
+		}
+		return document.NewDoubleValue(x), nil
+	case document.DurationValue:
+		x, _ := binary.Varint(data)
+		return document.NewDurationValue(time.Duration(x)), nil
+	case document.NullValue:
+		return document.NewNullValue(), nil
+	}
+
+	return document.Value{}, errors.New("unknown type")
+}
diff --git a/document/encoding/custom/format.go b/document/encoding/custom/format.go
new file mode 100644
index 00000000..419c6dde
--- /dev/null
+++ b/document/encoding/custom/format.go
@@ -0,0 +1,225 @@
+package custom
+
+import (
+	"bytes"
+	"encoding/binary"
+	"errors"
+	"io"
+)
+
+// Format is an encoding format used to encode and decode documents.
+// It is composed of a header and a body.
+// The header defines a list of fields, offsets and relevant metadata.
+// The body contains each fields data one concatenated one after another.
+type Format struct {
+	Header Header
+	Body   []byte
+}
+
+// Decode the given data into the format.
+func (f *Format) Decode(data []byte) error {
+	n, err := f.Header.Decode(data)
+	if err != nil {
+		return err
+	}
+
+	f.Body = data[n:]
+	return nil
+}
+
+// A Header contains a representation of a document's metadata.
+type Header struct {
+	// Size of the header
+	Size uint64
+	// Number of fields
+	FieldsCount uint64
+	// List of headers for all the fields.
+	FieldHeaders []FieldHeader
+}
+
+// Decode data into the header.
+func (h *Header) Decode(data []byte) (int, error) {
+	var n int
+
+	h.Size, n = binary.Uvarint(data)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+
+	hdata := data[n : n+int(h.Size)]
+	read := n + int(h.Size)
+
+	h.FieldsCount, n = binary.Uvarint(hdata)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+	hdata = hdata[n:]
+
+	h.FieldHeaders = make([]FieldHeader, 0, int(h.FieldsCount))
+	for len(hdata) > 0 {
+		var fh FieldHeader
+		n, err := fh.Decode(hdata)
+		if err != nil {
+			return 0, err
+		}
+		hdata = hdata[n:]
+
+		h.FieldHeaders = append(h.FieldHeaders, fh)
+	}
+
+	return read, nil
+}
+
+// BodySize returns the size of the body.
+func (h *Header) BodySize() int {
+	var size uint64
+
+	for _, fh := range h.FieldHeaders {
+		size += fh.Size
+	}
+
+	return int(size)
+}
+
+// WriteTo encodes the header into w.
+func (h *Header) WriteTo(w io.Writer) (int64, error) {
+	intBuf := make([]byte, binary.MaxVarintLen64)
+	var buf bytes.Buffer
+
+	// number of fields
+	h.FieldsCount = uint64(len(h.FieldHeaders))
+	n := binary.PutUvarint(intBuf, h.FieldsCount)
+	_, err := buf.Write(intBuf[:n])
+	if err != nil {
+		return 0, err
+	}
+
+	for _, fh := range h.FieldHeaders {
+		_, err := fh.WriteTo(&buf)
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	// header size
+	h.Size = uint64(buf.Len())
+	n = binary.PutUvarint(intBuf, h.Size)
+	_, err = w.Write(intBuf[:n])
+	if err != nil {
+		return 0, err
+	}
+
+	return io.Copy(w, &buf)
+}
+
+// FieldHeader represents the metadata of a field.
+type FieldHeader struct {
+	// Size of the name of the field
+	NameSize uint64
+	// Name of the field
+	Name []byte
+	// Type of the field, corresponds to the Type
+	Type uint64
+	// Size of the data of the field
+	Size uint64
+	// Offset describing where the field is located, starting
+	// from the end of the format header.
+	Offset uint64
+
+	NameString string // used for encoding
+	buf        [binary.MaxVarintLen64]byte
+}
+
+// Decode the data into the field header.
+func (f *FieldHeader) Decode(data []byte) (int, error) {
+	var n, read int
+
+	// name size
+	f.NameSize, n = binary.Uvarint(data)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+	data = data[n:]
+	read += n
+
+	// name
+	f.Name = data[:f.NameSize]
+	data = data[f.NameSize:]
+	read += int(f.NameSize)
+
+	// type
+	f.Type, n = binary.Uvarint(data)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+	data = data[n:]
+	read += n
+
+	// size
+	f.Size, n = binary.Uvarint(data)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+	data = data[n:]
+	read += n
+
+	// offset
+	f.Offset, n = binary.Uvarint(data)
+	if n <= 0 {
+		return 0, errors.New("cannot decode data")
+	}
+	data = data[n:]
+	read += n
+
+	return read, nil
+}
+
+// WriteTo encodes the field header into w.
+func (f *FieldHeader) WriteTo(w io.Writer) (int64, error) {
+	var written int
+
+	// name size
+	n := binary.PutUvarint(f.buf[:], f.NameSize)
+	_, err := w.Write(f.buf[:n])
+	if err != nil {
+		return 0, err
+	}
+	written += n
+
+	// name
+	if buf, ok := w.(*bytes.Buffer); ok && f.NameString != "" {
+		n, err = buf.WriteString(f.NameString)
+	} else {
+		n, err = w.Write(f.Name)
+	}
+	if err != nil {
+		return 0, err
+	}
+	written += n
+
+	// type
+	n = binary.PutUvarint(f.buf[:], f.Type)
+	_, err = w.Write(f.buf[:n])
+	if err != nil {
+		return 0, err
+	}
+	written += n
+
+	// size
+	n = binary.PutUvarint(f.buf[:], f.Size)
+	_, err = w.Write(f.buf[:n])
+	if err != nil {
+		return 0, err
+	}
+	written += n
+
+	// offset
+	n = binary.PutUvarint(f.buf[:], f.Offset)
+	_, err = w.Write(f.buf[:n])
+	if err != nil {
+		return 0, err
+	}
+	written += n
+
+	return int64(written), nil
+}
diff --git a/document/encoding/msgpack/encoding.go b/document/encoding/msgpack/encoding.go
index e3d4ebbd..b1ad62cb 100644
--- a/document/encoding/msgpack/encoding.go
+++ b/document/encoding/msgpack/encoding.go
@@ -193,14 +193,7 @@ func (e EncodedDocument) Iterate(fn func(field string, value document.Value) err
 
 // MarshalJSON implements the json.Marshaler interface.
 func (e EncodedDocument) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	err := document.ToJSON(&buf, e)
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
+	return document.MarshalJSON(e)
 }
 
 // An EncodedArray implements the document.Array interface on top of an
@@ -263,14 +256,7 @@ func (e EncodedArray) GetByIndex(idx int) (v document.Value, err error) {
 
 // MarshalJSON implements the json.Marshaler interface.
 func (e EncodedArray) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	err := document.ArrayToJSON(&buf, e)
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
+	return document.MarshalJSONArray(e)
 }
 
 // EncodeArray encodes a document.Array into its binary representation.
diff --git a/document/iterator.go b/document/iterator.go
index fb7a91e8..c23afe04 100644
--- a/document/iterator.go
+++ b/document/iterator.go
@@ -1,7 +1,9 @@
 package document
 
 import (
+	"bufio"
 	"errors"
+	"io"
 )
 
 // ErrStreamClosed is used to indicate that a stream must be closed.
@@ -43,6 +45,52 @@ func (f IteratorFunc) Iterate(fn func(d Document) error) error {
 	return f(fn)
 }
 
+// IteratorToJSON encodes all the documents of an iterator to JSON stream.
+func IteratorToJSON(w io.Writer, s Iterator) error {
+	buf := bufio.NewWriter(w)
+	defer buf.Flush()
+
+	return s.Iterate(func(d Document) error {
+		data, err := jsonDocument{d}.MarshalJSON()
+		if err != nil {
+			return err
+		}
+
+		_, err = buf.Write(data)
+		return err
+	})
+}
+
+// IteratorToJSONArray encodes all the documents of an iterator to a JSON array.
+func IteratorToJSONArray(w io.Writer, s Iterator) error {
+	buf := bufio.NewWriter(w)
+
+	buf.WriteByte('[')
+
+	first := true
+	err := s.Iterate(func(d Document) error {
+		if !first {
+			buf.WriteString(", ")
+		} else {
+			first = false
+		}
+
+		data, err := jsonDocument{d}.MarshalJSON()
+		if err != nil {
+			return err
+		}
+
+		_, err = buf.Write(data)
+		return err
+	})
+	if err != nil {
+		return err
+	}
+
+	buf.WriteByte(']')
+	return buf.Flush()
+}
+
 // Stream reads documents of an iterator one by one and passes them
 // through a list of functions for transformation.
 type Stream struct {
diff --git a/document/json.go b/document/json.go
deleted file mode 100644
index 55827050..00000000
--- a/document/json.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// +build !wasm
-
-package document
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"strconv"
-	"time"
-)
-
-// NewFromJSON creates a document from a JSON object.
-func NewFromJSON(data []byte) (Document, error) {
-	var fb FieldBuffer
-	err := json.Unmarshal(data, &fb)
-	if err != nil {
-		return nil, err
-	}
-	return &fb, nil
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (v Value) MarshalJSON() ([]byte, error) {
-	var x interface{}
-
-	switch v.Type {
-	case DocumentValue:
-		return jsonDocument{v.V.(Document)}.MarshalJSON()
-	case ArrayValue:
-		return jsonArray{v.V.(Array)}.MarshalJSON()
-	case TextValue:
-		x = string(v.V.(string))
-	case DurationValue:
-		x = v.V.(time.Duration).String()
-	default:
-		x = v.V
-	}
-
-	var buf bytes.Buffer
-	// json.Marshal uses HTML escaping by default
-	// which causes characters like > to be transformed
-	// into \u003c.
-	// to disable that we need to use json.Encoder instead
-	// and call SetEscapeHTML with false.
-	enc := json.NewEncoder(&buf)
-	enc.SetEscapeHTML(false)
-	err := enc.Encode(x)
-	if err != nil {
-		return nil, err
-	}
-
-	// json.Encoder always terminates each value with a newline,
-	// we need to remove it.
-	return buf.Bytes()[:buf.Len()-1], nil
-}
-
-// String returns a string representation of the value. It implements the fmt.Stringer interface.
-func (v Value) String() string {
-	switch v.Type {
-	case NullValue:
-		return "NULL"
-	case TextValue:
-		return strconv.Quote(v.V.(string))
-	case BlobValue, DurationValue:
-		return fmt.Sprintf("%v", v.V)
-	}
-
-	d, _ := v.MarshalJSON()
-	return string(d)
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (fb *FieldBuffer) MarshalJSON() ([]byte, error) {
-	return json.Marshal(jsonDocument{Document: fb})
-}
-
-// UnmarshalJSON implements the json.Unmarshaler interface.
-func (fb *FieldBuffer) UnmarshalJSON(data []byte) error {
-	dec := json.NewDecoder(bytes.NewReader(data))
-
-	t, err := dec.Token()
-	if err == io.EOF {
-		return err
-	}
-
-	return parseJSONDocument(dec, t, fb)
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (vb *ValueBuffer) MarshalJSON() ([]byte, error) {
-	return json.Marshal(jsonArray{Array: vb})
-}
-
-// UnmarshalJSON implements the json.Unmarshaler interface.
-func (vb *ValueBuffer) UnmarshalJSON(data []byte) error {
-	dec := json.NewDecoder(bytes.NewReader(data))
-
-	t, err := dec.Token()
-	if err == io.EOF {
-		return err
-	}
-
-	return parseJSONArray(dec, t, vb)
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (m mapDocument) MarshalJSON() ([]byte, error) {
-	return json.Marshal(jsonDocument{Document: m})
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (m structDocument) MarshalJSON() ([]byte, error) {
-	return json.Marshal(jsonDocument{Document: m})
-}
-
-// ToJSON encodes d to w in JSON.
-func ToJSON(w io.Writer, d Document) error {
-	buf, err := jsonDocument{d}.MarshalJSON()
-	if err != nil {
-		return err
-	}
-
-	_, err = w.Write(buf)
-	return err
-}
-
-// ArrayToJSON encodes a to w in JSON.
-func ArrayToJSON(w io.Writer, a Array) error {
-	buf, err := jsonArray{a}.MarshalJSON()
-	if err != nil {
-		return err
-	}
-
-	_, err = w.Write(buf)
-	return err
-}
-
-type jsonArray struct {
-	Array
-}
-
-func (j jsonArray) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	buf.WriteByte('[')
-	var notFirst bool
-	err := j.Array.Iterate(func(i int, v Value) error {
-		if notFirst {
-			buf.WriteString(", ")
-		}
-		notFirst = true
-
-		data, err := v.MarshalJSON()
-		if err != nil {
-			return err
-		}
-
-		_, err = buf.Write(data)
-		return err
-	})
-	if err != nil {
-		return nil, err
-	}
-	buf.WriteByte(']')
-	return buf.Bytes(), nil
-}
-
-type jsonDocument struct {
-	Document
-}
-
-func (j jsonDocument) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	buf.WriteByte('{')
-
-	var notFirst bool
-	err := j.Document.Iterate(func(f string, v Value) error {
-		if notFirst {
-			buf.WriteString(", ")
-		}
-		notFirst = true
-
-		buf.WriteString(strconv.Quote(f))
-		buf.WriteString(": ")
-
-		data, err := v.MarshalJSON()
-		if err != nil {
-			return err
-		}
-		_, err = buf.Write(data)
-		return err
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	buf.WriteByte('}')
-
-	return buf.Bytes(), nil
-}
-
-func parseJSONValue(dec *json.Decoder) (Value, error) {
-	// ensure the decoder parses numbers as the json.Number type
-	dec.UseNumber()
-
-	// parse the first token to determine which type is it
-	t, err := dec.Token()
-	if err != nil {
-		return Value{}, err
-	}
-
-	switch tt := t.(type) {
-	case string:
-		return NewTextValue(tt), nil
-	case bool:
-		return NewBoolValue(tt), nil
-	case nil:
-		return NewNullValue(), nil
-	case json.Number:
-		i, err := tt.Int64()
-		if err != nil {
-			// if it's too big to fit in an int64, let's try parsing this as a floating point number
-			f, err := tt.Float64()
-			if err != nil {
-				return Value{}, err
-			}
-
-			return NewDoubleValue(f), nil
-		}
-
-		return NewIntegerValue(int64(i)), nil
-	case json.Delim:
-		switch tt {
-		case ']', '}':
-			return Value{}, fmt.Errorf("found %q, expected '{' or '['", tt)
-		case '[':
-			buf := NewValueBuffer()
-			err := parseJSONArray(dec, t, &buf)
-			if err != nil {
-				return Value{}, err
-			}
-
-			return NewArrayValue(buf), nil
-		case '{':
-			buf := NewFieldBuffer()
-			err = parseJSONDocument(dec, t, buf)
-			if err != nil {
-				return Value{}, err
-			}
-
-			return NewDocumentValue(buf), nil
-		}
-	}
-
-	return Value{}, nil
-}
-
-func parseJSONDocument(dec *json.Decoder, currToken json.Token, buf *FieldBuffer) error {
-	var err error
-
-	// expecting a '{'
-	if d, ok := currToken.(json.Delim); !ok || d.String() != "{" {
-		return fmt.Errorf("found %q, expected '{'", d.String())
-	}
-
-	for dec.More() {
-		// parse the key, it must be a string
-		t, err := dec.Token()
-		if err != nil {
-			return err
-		}
-
-		k, ok := t.(string)
-		if !ok {
-			return fmt.Errorf("found %q, expected string", t)
-		}
-
-		v, err := parseJSONValue(dec)
-		if err != nil {
-			return err
-		}
-
-		buf.Add(k, v)
-	}
-
-	currToken, err = dec.Token()
-	if err == io.EOF {
-		return fmt.Errorf("found %q, expected '}'", err)
-	}
-
-	// expecting a '}'
-	if d, ok := currToken.(json.Delim); !ok || d.String() != "}" {
-		return fmt.Errorf("found %q, expected '}'", d.String())
-	}
-
-	return nil
-}
-
-func parseJSONArray(dec *json.Decoder, currToken json.Token, buf *ValueBuffer) error {
-	// expecting a '['
-	if d, ok := currToken.(json.Delim); !ok || d.String() != "[" {
-		return fmt.Errorf("found %q, expected '['", d.String())
-	}
-
-	for dec.More() {
-		v, err := parseJSONValue(dec)
-		if err != nil {
-			return err
-		}
-		*buf = buf.Append(v)
-	}
-
-	// expecting ']'
-	t, err := dec.Token()
-	if err != nil {
-		return err
-	}
-	if d, ok := t.(json.Delim); !ok || d != ']' {
-		return fmt.Errorf("found %q, expected ']'", t)
-	}
-
-	return nil
-}
-
-// IteratorToJSON encodes all the documents of an iterator to JSON stream.
-func IteratorToJSON(w io.Writer, s Iterator) error {
-	enc := json.NewEncoder(w)
-	enc.SetEscapeHTML(false)
-	enc.SetIndent("", "  ")
-
-	return s.Iterate(func(d Document) error {
-		return enc.Encode(jsonDocument{d})
-	})
-}
-
-// IteratorToJSONArray encodes all the documents of an iterator to a JSON array.
-func IteratorToJSONArray(w io.Writer, s Iterator) error {
-	buf := bufio.NewWriter(w)
-
-	buf.WriteByte('[')
-
-	first := true
-	err := s.Iterate(func(d Document) error {
-		if !first {
-			buf.WriteString(", ")
-		} else {
-			first = false
-		}
-
-		return ToJSON(buf, d)
-	})
-	if err != nil {
-		return err
-	}
-
-	buf.WriteByte(']')
-	return buf.Flush()
-}
diff --git a/document/value.go b/document/value.go
index df1ef89e..926cb0f1 100644
--- a/document/value.go
+++ b/document/value.go
@@ -2,10 +2,14 @@ package document
 
 import (
 	"bytes"
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"math"
+	"strconv"
 	"time"
+
+	"github.com/buger/jsonparser"
 )
 
 var (
@@ -254,6 +258,61 @@ func (v Value) IsZeroValue() (bool, error) {
 	return false, nil
 }
 
+// MarshalJSON implements the json.Marshaler interface.
+func (v Value) MarshalJSON() ([]byte, error) {
+	switch v.Type {
+	case NullValue:
+		return []byte("null"), nil
+	case BoolValue:
+		return strconv.AppendBool(nil, v.V.(bool)), nil
+	case IntegerValue:
+		return strconv.AppendInt(nil, v.V.(int64), 10), nil
+	case DoubleValue:
+		f := v.V.(float64)
+		abs := math.Abs(f)
+		fmt := byte('f')
+		if abs != 0 {
+			if abs < 1e-6 || abs >= 1e21 {
+				fmt = 'e'
+			}
+		}
+
+		return strconv.AppendFloat(nil, v.V.(float64), fmt, -1, 64), nil
+	case DurationValue:
+		return []byte(strconv.Quote(v.V.(time.Duration).String())), nil
+	case TextValue:
+		return []byte(strconv.Quote(v.V.(string))), nil
+	case BlobValue:
+		src := v.V.([]byte)
+		dst := make([]byte, base64.StdEncoding.EncodedLen(len(src))+2)
+		dst[0] = '"'
+		dst[len(dst)-1] = '"'
+		base64.StdEncoding.Encode(dst[1:], src)
+		return dst, nil
+	case ArrayValue:
+		return jsonArray{v.V.(Array)}.MarshalJSON()
+	case DocumentValue:
+		return jsonDocument{v.V.(Document)}.MarshalJSON()
+	default:
+		return nil, errors.New("unexpected type: " + v.Type.String())
+	}
+}
+
+// String returns a string representation of the value. It implements the fmt.Stringer interface.
+func (v Value) String() string {
+	switch v.Type {
+	case NullValue:
+		return "NULL"
+	case TextValue:
+		return strconv.Quote(v.V.(string))
+	case BlobValue, DurationValue:
+		return fmt.Sprintf("%v", v.V)
+	}
+
+	d, _ := v.MarshalJSON()
+	return string(d)
+}
+
 // Add u to v and return the result.
 // Only numeric values and booleans can be added together.
 func (v Value) Add(u Value) (res Value, err error) {
@@ -478,3 +537,54 @@ func calculateFloats(a, b Value, operator byte) (res Value, err error) {
 		panic(fmt.Sprintf("unknown operator %c", operator))
 	}
 }
+
+func parseJSONValue(dataType jsonparser.ValueType, data []byte) (v Value, err error) {
+	switch dataType {
+	case jsonparser.Null:
+		return NewNullValue(), nil
+	case jsonparser.Boolean:
+		b, err := jsonparser.ParseBoolean(data)
+		if err != nil {
+			return Value{}, err
+		}
+		return NewBoolValue(b), nil
+	case jsonparser.Number:
+		i, err := jsonparser.ParseInt(data)
+		if err != nil {
+			// if it's too big to fit in an int64, let's try parsing this as a floating point number
+			f, err := jsonparser.ParseFloat(data)
+			if err != nil {
+				return Value{}, err
+			}
+
+			return NewDoubleValue(f), nil
+		}
+
+		return NewIntegerValue(i), nil
+	case jsonparser.String:
+		s, err := jsonparser.ParseString(data)
+		if err != nil {
+			return Value{}, err
+		}
+		return NewTextValue(s), nil
+	case jsonparser.Array:
+		buf := NewValueBuffer()
+		err := buf.UnmarshalJSON(data)
+		if err != nil {
+			return Value{}, err
+		}
+
+		return NewArrayValue(buf), nil
+	case jsonparser.Object:
+		buf := NewFieldBuffer()
+		err = buf.UnmarshalJSON(data)
+		if err != nil {
+			return Value{}, err
+		}
+
+		return NewDocumentValue(buf), nil
+	default:
+	}
+
+	return Value{}, nil
+}
diff --git a/go.mod b/go.mod
index 59c1fe33..be4be253 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/genjidb/genji
 go 1.15
 
 require (
+	github.com/buger/jsonparser v1.0.0
 	github.com/google/btree v1.0.0
 	github.com/stretchr/testify v1.6.1
 	github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1
diff --git a/go.sum b/go.sum
index 13ffbf9e..a25b7180 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/buger/jsonparser v1.0.0 h1:etJTGF5ESxjI0Ic2UaLQs2LQQpa8G9ykQScukbh4L8A=
+github.com/buger/jsonparser v1.0.0/go.mod h1:tgcrVJ81GPSF0mz+0nu1Xaz0fazGPrmmJfJtxjbHhUQ=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -25,8 +27,6 @@ github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1 h1:d71/KA0LhvkrJ/Ok+Wx9qK7bU8meK
 github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=
 github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
 github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
-go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
-go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -44,7 +44,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
diff --git a/new.go b/new.go
new file mode 100644
index 00000000..e87d7738
--- /dev/null
+++ b/new.go
@@ -0,0 +1,21 @@
+// +build !wasm
+
+package genji
+
+import (
+	"github.com/genjidb/genji/database"
+	"github.com/genjidb/genji/document/encoding/msgpack"
+	"github.com/genjidb/genji/engine"
+)
+
+// New initializes the DB using the given engine.
+func New(ng engine.Engine) (*DB, error) {
+	db, err := database.New(ng, database.Options{Codec: msgpack.NewCodec()})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DB{
+		DB: db,
+	}, nil
+}
diff --git a/new_wasm.go b/new_wasm.go
new file mode 100644
index 00000000..4513a209
--- /dev/null
+++ b/new_wasm.go
@@ -0,0 +1,21 @@
+// +build wasm
+
+package genji
+
+import (
+	"github.com/genjidb/genji/database"
+	"github.com/genjidb/genji/document/encoding/custom"
+	"github.com/genjidb/genji/engine"
+)
+
+// New initializes the DB using the given engine.
+func New(ng engine.Engine) (*DB, error) {
+	db, err := database.New(ng, database.Options{Codec: custom.NewCodec()})
+	if err != nil {
+		return nil, err
+	}
+
+	return &DB{
+		DB: db,
+	}, nil
+}
diff --git a/open.go b/open.go
index 8139a9f2..8e963d84 100644
--- a/open.go
+++ b/open.go
@@ -9,7 +9,7 @@ import (
 )
 
 // Open creates a Genji database at the given path.
-// If path is equal to ":memory:" it will open an in memory database,
+// If path is equal to ":memory:" it will open an in-memory database,
 // otherwise it will create an on-disk database using the BoltDB engine.
 func Open(path string) (*DB, error) {
 	var ng engine.Engine
diff --git a/params_wasm.go b/params_wasm.go
index b7b97cee..169ca348 100644
--- a/params_wasm.go
+++ b/params_wasm.go
@@ -1,16 +1,16 @@
 package genji
 
 import (
-	"github.com/genjidb/genji/sql/query"
+	"github.com/genjidb/genji/sql/query/expr"
 )
 
-func argsToParams(args []interface{}) []query.Param {
-	nv := make([]query.Param, len(args))
+func argsToParams(args []interface{}) []expr.Param {
+	nv := make([]expr.Param, len(args))
 	for i := range args {
 		switch t := args[i].(type) {
-		case *query.Param:
+		case *expr.Param:
 			nv[i] = *t
-		case query.Param:
+		case expr.Param:
 			nv[i] = t
 		default:
 			nv[i].Value = args[i]
diff --git a/sql/planner/projection.go b/sql/planner/projection.go
index 3fe051dd..77ef8741 100644
--- a/sql/planner/projection.go
+++ b/sql/planner/projection.go
@@ -1,7 +1,6 @@
 package planner
 
 import (
-	"bytes"
 	"errors"
 	"fmt"
 	"strings"
@@ -91,7 +90,7 @@ func (n *ProjectionNode) toStream(st document.Stream) (document.Stream, error) {
 			return st, err
 		}
 
-		st = document.NewStream(document.NewIterator(fb))
+		st = document.NewStream(document.NewIterator(&fb))
 	} else {
 		var dm documentMask
 		st = st.Map(func(d document.Document) (document.Document, error) {
@@ -155,14 +154,7 @@ func (r documentMask) Iterate(fn func(field string, value document.Value) error)
 
 // MarshalJSON implements the json.Marshaler interface.
 func (r documentMask) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-
-	err := document.ToJSON(&buf, r)
-	if err != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
+	return document.MarshalJSON(r)
 }
 
 // A ProjectedField is a field that will be part of the projected document that will be returned at the end of a Select statement.


Test patch:
diff --git a/database/config_test.go b/database/config_test.go
index c56ffa95..6a7a5a5a 100644
--- a/database/config_test.go
+++ b/database/config_test.go
@@ -36,7 +36,7 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng)
+		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 		defer db.Close()
 
@@ -79,7 +79,7 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng)
+		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 		defer db.Close()
 
@@ -106,7 +106,7 @@ func TestTableInfoStore(t *testing.T) {
 		ng := memoryengine.NewEngine()
 		defer ng.Close()
 
-		db, err := New(ng)
+		db, err := New(ng, Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 		defer db.Close()
 
diff --git a/database/table_test.go b/database/table_test.go
index 9dce749e..2260f4cd 100644
--- a/database/table_test.go
+++ b/database/table_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/genjidb/genji/key"
 	"github.com/genjidb/genji/sql/parser"
@@ -151,7 +152,7 @@ func TestTableInsert(t *testing.T) {
 	t.Run("Should generate the right docid on existing databases", func(t *testing.T) {
 		ng := memoryengine.NewEngine()
 
-		db, err := database.New(ng)
+		db, err := database.New(ng, database.Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 
 		insertDoc := func(db *database.Database) []byte {
@@ -178,7 +179,7 @@ func TestTableInsert(t *testing.T) {
 		require.NoError(t, err)
 
 		// create new database object
-		db, err = database.New(ng)
+		db, err = database.New(ng, database.Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 
 		key2 := insertDoc(db)
diff --git a/database/transaction_test.go b/database/transaction_test.go
index f4ffd5fb..3a99ebbf 100644
--- a/database/transaction_test.go
+++ b/database/transaction_test.go
@@ -5,13 +5,14 @@ import (
 
 	"github.com/genjidb/genji/database"
 	"github.com/genjidb/genji/document"
+	"github.com/genjidb/genji/document/encoding/msgpack"
 	"github.com/genjidb/genji/engine/memoryengine"
 	"github.com/genjidb/genji/key"
 	"github.com/stretchr/testify/require"
 )
 
 func newTestDB(t testing.TB) (*database.Transaction, func()) {
-	db, err := database.New(memoryengine.NewEngine())
+	db, err := database.New(memoryengine.NewEngine(), database.Options{Codec: msgpack.NewCodec()})
 	require.NoError(t, err)
 
 	tx, err := db.Begin(true)
@@ -45,7 +46,7 @@ func TestTxTable(t *testing.T) {
 	})
 
 	t.Run("Create and rollback", func(t *testing.T) {
-		db, err := database.New(memoryengine.NewEngine())
+		db, err := database.New(memoryengine.NewEngine(), database.Options{Codec: msgpack.NewCodec()})
 		require.NoError(t, err)
 		defer db.Close()
 
diff --git a/document/document_test.go b/document/document_test.go
index a6dab38e..ec5784cd 100644
--- a/document/document_test.go
+++ b/document/document_test.go
@@ -1,7 +1,6 @@
 package document_test
 
 import (
-	"bytes"
 	"encoding/json"
 	"errors"
 	"testing"
@@ -132,11 +131,9 @@ func TestFieldBuffer(t *testing.T) {
 				}
 
 				require.NoError(t, err)
-				var bufBytes bytes.Buffer
+				data, err := document.MarshalJSON(fb)
 				require.NoError(t, err)
-				err = document.ToJSON(&bufBytes, fb)
-				require.NoError(t, err)
-				require.Equal(t, tt.want, bufBytes.String())
+				require.Equal(t, tt.want, string(data))
 			})
 		}
 	})
@@ -601,6 +598,48 @@ func TestValuePath(t *testing.T) {
 	}
 }
 
+func TestJSONDocument(t *testing.T) {
+	tests := []struct {
+		name     string
+		d        document.Document
+		expected string
+	}{
+		{
+			"Flat",
+			document.NewFieldBuffer().
+				Add("name", document.NewTextValue("John")).
+				Add("age", document.NewIntegerValue(10)).
+				Add(`"something with" quotes`, document.NewIntegerValue(10)),
+			`{"name":"John","age":10,"\"something with\" quotes":10}`,
+		},
+		{
+			"Nested",
+			document.NewFieldBuffer().
+				Add("name", document.NewTextValue("John")).
+				Add("age", document.NewIntegerValue(10)).
+				Add("address", document.NewDocumentValue(document.NewFieldBuffer().
+					Add("city", document.NewTextValue("Ajaccio")).
+					Add("country", document.NewTextValue("France")),
+				)).
+				Add("friends", document.NewArrayValue(
+					document.NewValueBuffer().
+						Append(document.NewTextValue("fred")).
+						Append(document.NewTextValue("jamie")),
+				)),
+			`{"name":"John","age":10,"address":{"city":"Ajaccio","country":"France"},"friends":["fred","jamie"]}`,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			data, err := json.Marshal(test.d)
+			require.NoError(t, err)
+			require.Equal(t, test.expected, string(data))
+			require.NoError(t, err)
+		})
+	}
+}
+
 func BenchmarkDocumentIterate(b *testing.B) {
 	f := foo{
 		A: "a",
diff --git a/document/encoding/custom/encoding_test.go b/document/encoding/custom/encoding_test.go
new file mode 100644
index 00000000..1d2f9cff
--- /dev/null
+++ b/document/encoding/custom/encoding_test.go
@@ -0,0 +1,20 @@
+package custom
+
+import (
+	"testing"
+
+	"github.com/genjidb/genji/document/encoding"
+	"github.com/genjidb/genji/document/encoding/encodingtest"
+)
+
+func TestCodec(t *testing.T) {
+	encodingtest.TestCodec(t, func() encoding.Codec {
+		return NewCodec()
+	})
+}
+
+func BenchmarkCodec(b *testing.B) {
+	encodingtest.BenchmarkCodec(b, func() encoding.Codec {
+		return NewCodec()
+	})
+}
diff --git a/document/encoding/encodingtest/testing.go b/document/encoding/encodingtest/testing.go
index c1e877e2..fece4449 100644
--- a/document/encoding/encodingtest/testing.go
+++ b/document/encoding/encodingtest/testing.go
@@ -84,10 +84,9 @@ func testEncodeDecode(t *testing.T, codecBuilder func() encoding.Codec) {
 			codec := codecBuilder()
 			err := codec.NewEncoder(&buf).EncodeDocument(test.d)
 			require.NoError(t, err)
-			var jsonBuf bytes.Buffer
-			err = document.ToJSON(&jsonBuf, codec.NewDocument(buf.Bytes()))
+			data, err := document.MarshalJSON(codec.NewDocument(buf.Bytes()))
 			require.NoError(t, err)
-			require.JSONEq(t, test.expected, jsonBuf.String())
+			require.JSONEq(t, test.expected, string(data))
 		})
 	}
 }
@@ -126,7 +125,7 @@ func testArrayGetByIndex(t *testing.T, codecBuilder func() encoding.Codec) {
 	require.Equal(t, document.NewTextValue("john"), v)
 
 	v, err = a.GetByIndex(1000)
-	require.Equal(t, err, document.ErrValueNotFound)
+	require.Equal(t, document.ErrValueNotFound, err)
 }
 
 func testDecodeDocument(t *testing.T, codecBuilder func() encoding.Codec) {
@@ -154,12 +153,11 @@ func testDecodeDocument(t *testing.T, codecBuilder func() encoding.Codec) {
 	require.Equal(t, document.NewIntegerValue(10), v)
 	v, err = ec.GetByField("address")
 	require.NoError(t, err)
-	var expected, actual bytes.Buffer
-	err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
+	expected, err := document.MarshalJSON(document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
 	require.NoError(t, err)
-	err = document.ToJSON(&actual, document.NewFieldBuffer().Add("address", v))
+	actual, err := document.MarshalJSON(document.NewFieldBuffer().Add("address", v))
 	require.NoError(t, err)
-	require.JSONEq(t, expected.String(), actual.String())
+	require.JSONEq(t, string(expected), string(actual))
 
 	var i int
 	err = ec.Iterate(func(f string, v document.Value) error {
@@ -167,12 +165,11 @@ func testDecodeDocument(t *testing.T, codecBuilder func() encoding.Codec) {
 		case "age":
 			require.Equal(t, document.NewIntegerValue(10), v)
 		case "address":
-			var expected, actual bytes.Buffer
-			err = document.ToJSON(&expected, document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
+			expected, err := document.MarshalJSON(document.NewFieldBuffer().Add("address", document.NewDocumentValue(mapDoc)))
 			require.NoError(t, err)
-			err = document.ToJSON(&actual, document.NewFieldBuffer().Add(f, v))
+			actual, err := document.MarshalJSON(document.NewFieldBuffer().Add(f, v))
 			require.NoError(t, err)
-			require.JSONEq(t, expected.String(), actual.String())
+			require.JSONEq(t, string(expected), string(actual))
 		case "name":
 			require.Equal(t, document.NewTextValue("john"), v)
 		}
diff --git a/document/iterator_test.go b/document/iterator_test.go
index ee99a234..dad203a2 100644
--- a/document/iterator_test.go
+++ b/document/iterator_test.go
@@ -1,11 +1,15 @@
 package document_test
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"log"
+	"testing"
 
 	"github.com/genjidb/genji"
 	"github.com/genjidb/genji/document"
+	"github.com/stretchr/testify/require"
 )
 
 func ExampleStream_First() {
@@ -145,3 +149,19 @@ func ExampleStream_Iterate() {
 	// {10 foo10 100 {Lyon 69010}}
 	// 10 foo10 100 map[city:Lyon zipcode:69010]
 }
+
+func TestIteratorToJSONArray(t *testing.T) {
+	var docs []document.Document
+	for i := 0; i < 3; i++ {
+		fb := document.NewFieldBuffer()
+		err := json.Unmarshal([]byte(fmt.Sprintf(`{"a": %d}`, i)), fb)
+		require.NoError(t, err)
+		docs = append(docs, fb)
+	}
+
+	it := document.NewIterator(docs...)
+	var buf bytes.Buffer
+	err := document.IteratorToJSONArray(&buf, it)
+	require.NoError(t, err)
+	require.Equal(t, `[{"a": 0}, {"a": 1}, {"a": 2}]`, buf.String())
+}
diff --git a/document/json_test.go b/document/json_test.go
deleted file mode 100644
index 0e4a9428..00000000
--- a/document/json_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package document_test
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"testing"
-
-	"github.com/genjidb/genji/document"
-	"github.com/stretchr/testify/require"
-)
-
-func TestToJSON(t *testing.T) {
-	tests := []struct {
-		name     string
-		r        document.Document
-		expected string
-	}{
-		{
-			"Flat",
-			document.NewFieldBuffer().
-				Add("name", document.NewTextValue("John")).
-				Add("age", document.NewIntegerValue(10)).
-				Add(`"something with" quotes`, document.NewIntegerValue(10)),
-			`{"name": "John", "age": 10, "\"something with\" quotes": 10}`,
-		},
-		{
-			"Nested",
-			document.NewFieldBuffer().
-				Add("name", document.NewTextValue("John")).
-				Add("age", document.NewIntegerValue(10)).
-				Add("address", document.NewDocumentValue(document.NewFieldBuffer().
-					Add("city", document.NewTextValue("Ajaccio")).
-					Add("country", document.NewTextValue("France")),
-				)).
-				Add("friends", document.NewArrayValue(
-					document.NewValueBuffer().
-						Append(document.NewTextValue("fred")).
-						Append(document.NewTextValue("jamie")),
-				)),
-			`{"name": "John", "age": 10, "address": {"city": "Ajaccio", "country": "France"}, "friends": ["fred", "jamie"]}`,
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			var buf bytes.Buffer
-			err := document.ToJSON(&buf, test.r)
-			require.NoError(t, err)
-			require.Equal(t, test.expected, buf.String())
-			require.NoError(t, err)
-		})
-	}
-}
-
-func TestIteratorToJSONArray(t *testing.T) {
-	var docs []document.Document
-	for i := 0; i < 3; i++ {
-		fb := document.NewFieldBuffer()
-		err := json.Unmarshal([]byte(fmt.Sprintf(`{"a": %d}`, i)), fb)
-		require.NoError(t, err)
-		docs = append(docs, fb)
-	}
-
-	it := document.NewIterator(docs...)
-	var buf bytes.Buffer
-	err := document.IteratorToJSONArray(&buf, it)
-	require.NoError(t, err)
-	require.Equal(t, `[{"a": 0}, {"a": 1}, {"a": 2}]`, buf.String())
-}
diff --git a/sql/query/alter_test.go b/sql/query/alter_test.go
index 8074477b..8ec108a2 100644
--- a/sql/query/alter_test.go
+++ b/sql/query/alter_test.go
@@ -1,7 +1,6 @@
 package query_test
 
 import (
-	"bytes"
 	"testing"
 
 	"github.com/genjidb/genji"
@@ -34,10 +33,9 @@ func TestAlterTable(t *testing.T) {
 	require.EqualError(t, err, database.ErrTableNotFound.Error())
 
 	d, err := db.QueryDocument("SELECT * FROM bar")
-	var buf bytes.Buffer
-	err = document.ToJSON(&buf, d)
+	data, err := document.MarshalJSON(d)
 	require.NoError(t, err)
-	require.JSONEq(t, `{"name": "John Doe", "age": 99}`, buf.String())
+	require.JSONEq(t, `{"name": "John Doe", "age": 99}`, string(data))
 
 	// Renaming a read-only table should fail
 	err = db.Exec("ALTER TABLE __genji_tables RENAME TO bar")
diff --git a/sql/query/select_test.go b/sql/query/select_test.go
index f7f23c36..810f5f82 100644
--- a/sql/query/select_test.go
+++ b/sql/query/select_test.go
@@ -174,10 +174,9 @@ func TestSelectStmt(t *testing.T) {
 
 			var i int
 			err = st.Iterate(func(d document.Document) error {
-				var buf bytes.Buffer
-				err = document.ToJSON(&buf, d)
+				data, err := document.MarshalJSON(d)
 				require.NoError(t, err)
-				require.JSONEq(t, res[i], buf.String())
+				require.JSONEq(t, res[i], string(data))
 				i++
 				return nil
 			})