New Upstream Release - golang-github-dsnet-golib

Ready changes

Summary

Merged new upstream version: 1.0.2 (was: 0.0~git20171103.1ea1667).

Resulting package

Built on 2022-11-25T00:10 (took 2m32s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-dsnet-golib-dev

Lintian Result

Diff

diff --git a/README.md b/README.md
index baaea89..1f033b0 100644
--- a/README.md
+++ b/README.md
@@ -7,20 +7,17 @@ Functionality that the author (Joe Tsai) found common among his various projects
 are pulled out and placed here.
 
 
-## Installation ##
-
-Run the command:
-
-```go get -u github.com/dsnet/golib```
-
-
 ## Packages ##
 
+Each top-level package is a sub-module so that the individual packages
+can be versioned separately from each other.
+
 | Package | Description |
 | :------ | :---------- |
-| [bufpipe](http://godoc.org/github.com/dsnet/golib/bufpipe) | Package bufpipe implements a buffered pipe. |
-| [cron](http://godoc.org/github.com/dsnet/golib/cron) | Package cron parses and runs cron schedules. |
-| [hashmerge](http://godoc.org/github.com/dsnet/golib/hashmerge) | Package hashmerge merges hash checksums. |
-| [jsonfmt](http://godoc.org/github.com/dsnet/golib/jsonfmt) | Package jsonfmt implements a JSON formatter. |
-| [memfile](http://godoc.org/github.com/dsnet/golib/memfile) | Package memfile implements an in-memory emulation of os.File. |
-| [unitconv](http://godoc.org/github.com/dsnet/golib/unitconv) | Package unitconv implements string conversion functionality for unit prefixes. |
+| [bufpipe](https://pkg.go.dev/github.com/dsnet/golib/bufpipe) | Package bufpipe implements a buffered pipe. |
+| [cron](https://pkg.go.dev/github.com/dsnet/golib/cron) | Package cron parses and runs cron schedules. |
+| [hashmerge](https://pkg.go.dev/github.com/dsnet/golib/hashmerge) | Package hashmerge merges hash checksums. |
+| [jsoncs](https://pkg.go.dev/github.com/dsnet/golib/jsoncs) | Package jsoncs implements JSON Canonicalization Scheme (JCS) as specified in RFC 8785. |
+| [jsonfmt](https://pkg.go.dev/github.com/dsnet/golib/jsonfmt) | Package jsonfmt implements a JSON formatter. |
+| [memfile](https://pkg.go.dev/github.com/dsnet/golib/memfile) | Package memfile implements an in-memory emulation of os.File. |
+| [unitconv](https://pkg.go.dev/github.com/dsnet/golib/unitconv) | Package unitconv implements string conversion functionality for unit prefixes. |
diff --git a/bufpipe/LICENSE.md b/bufpipe/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/bufpipe/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/bufpipe/go.mod b/bufpipe/go.mod
new file mode 100644
index 0000000..36aadea
--- /dev/null
+++ b/bufpipe/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/bufpipe
+
+go 1.12
diff --git a/cron/LICENSE.md b/cron/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/cron/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cron/go.mod b/cron/go.mod
new file mode 100644
index 0000000..85890a4
--- /dev/null
+++ b/cron/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/cron
+
+go 1.12
diff --git a/debian/changelog b/debian/changelog
index 589c213..a0fd0ca 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-dsnet-golib (1.0.2-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 25 Nov 2022 00:08:50 -0000
+
 golang-github-dsnet-golib (0.0~git20171103.1ea1667-1) unstable; urgency=medium
 
   * Initial release (Closes: #902735)
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..761bb47
--- /dev/null
+++ b/go.mod
@@ -0,0 +1 @@
+module github.com/dsnet/golib
diff --git a/hashmerge/LICENSE.md b/hashmerge/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/hashmerge/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hashmerge/go.mod b/hashmerge/go.mod
new file mode 100644
index 0000000..5e508c9
--- /dev/null
+++ b/hashmerge/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/hashmerge
+
+go 1.12
diff --git a/jsoncs/LICENSE.md b/jsoncs/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/jsoncs/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/jsoncs/decode.go b/jsoncs/decode.go
new file mode 100644
index 0000000..fd6f788
--- /dev/null
+++ b/jsoncs/decode.go
@@ -0,0 +1,340 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+	"unicode"
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+type (
+	jsonValue  = interface{}
+	jsonArray  = []jsonValue
+	jsonObject = map[string]jsonValue
+)
+
+var (
+	nullLiteral  = []byte("null")
+	trueLiteral  = []byte("true")
+	falseLiteral = []byte("false")
+)
+
+// decode unmarshals the JSON input as a JSON value.
+//
+// It is similar to:
+//
+//	var v interface{}
+//	err := json.Unmarshal(b, &v)
+//	return v, err
+//
+// However, it is stricter than the standard library implementation in that
+// it rejects JSON objects with duplicate keys and invalid UTF-8
+// in adherence of RFC 7493.
+func decode(b []byte) (jsonValue, error) {
+	v, b, err := decodeValue(b)
+	if err != nil {
+		return nil, err
+	}
+	if len(trimSpace(b)) > 0 {
+		return nil, errors.New("invalid data after top-level value")
+	}
+	return v, nil
+}
+
+// decodeValue unmarshals the next JSON value per RFC 7159, section 3.
+// It consume the leading value, and returns the remaining bytes.
+func decodeValue(b []byte) (jsonValue, []byte, error) {
+	b = trimSpace(b)
+	switch {
+	case len(b) == 0:
+		return nil, nil, io.ErrUnexpectedEOF
+	case b[0] == '{':
+		return decodeObject(b)
+	case b[0] == '[':
+		return decodeArray(b)
+	case b[0] == '"':
+		return decodeString(b)
+	case (b[0] == '-' || ('0' <= b[0] && b[0] <= '9')):
+		return decodeNumber(b)
+	case bytes.HasPrefix(b, nullLiteral):
+		return nil, b[len(nullLiteral):], nil
+	case bytes.HasPrefix(b, trueLiteral):
+		return true, b[len(trueLiteral):], nil
+	case bytes.HasPrefix(b, falseLiteral):
+		return false, b[len(falseLiteral):], nil
+	default:
+		return nil, b, errors.New("expected next JSON value")
+	}
+}
+
+// decodeObject unmarshals the next JSON object per RFC 7159, section 4,
+// with special attention paid to RFC 7493, section 2.3
+// regarding rejection of duplicate entry names.
+// It consume the leading value, and returns the remaining bytes.
+func decodeObject(b []byte) (jsonObject, []byte, error) {
+	b = trimSpace(b)
+	switch {
+	case len(b) == 0:
+		return nil, b, io.ErrUnexpectedEOF
+	case b[0] == '{':
+		b = b[1:]
+	default:
+		return nil, b, errors.New("expected '{' character in JSON object")
+	}
+
+	var init bool
+	var obj = make(jsonObject)
+	var err error
+	for {
+		b = trimSpace(b)
+		if len(b) > 0 && b[0] == '}' {
+			return obj, b[1:], nil
+		}
+
+		if init {
+			b = trimSpace(b)
+			switch {
+			case len(b) == 0:
+				return nil, b, io.ErrUnexpectedEOF
+			case b[0] == ',':
+				b = b[1:]
+			default:
+				return nil, b, errors.New("expected ',' character in JSON object")
+			}
+		}
+
+		var k string
+		k, b, err = decodeString(b)
+		if err != nil {
+			return nil, b, err
+		}
+		if _, ok := obj[k]; ok {
+			return nil, b, fmt.Errorf("duplicate key %q in JSON object", k)
+		}
+
+		b = trimSpace(b)
+		switch {
+		case len(b) == 0:
+			return nil, b, io.ErrUnexpectedEOF
+		case b[0] == ':':
+			b = b[1:]
+		default:
+			return nil, b, errors.New("expected ':' character in JSON object")
+		}
+
+		var v jsonValue
+		v, b, err = decodeValue(b)
+		if err != nil {
+			return nil, b, err
+		}
+
+		obj[k] = v
+		init = true
+	}
+}
+
+// decodeArray unmarshals the next JSON object per RFC 7159, section 5.
+// It consume the leading value, and returns the remaining bytes.
+func decodeArray(b []byte) (jsonArray, []byte, error) {
+	b = trimSpace(b)
+	switch {
+	case len(b) == 0:
+		return nil, b, io.ErrUnexpectedEOF
+	case b[0] == '[':
+		b = b[1:]
+	default:
+		return nil, b, errors.New("expected '[' character in JSON array")
+	}
+
+	var init bool
+	var arr jsonArray
+	var err error
+	for {
+		b = trimSpace(b)
+		if len(b) > 0 && b[0] == ']' {
+			return arr, b[1:], nil
+		}
+
+		if init {
+			b = trimSpace(b)
+			switch {
+			case len(b) == 0:
+				return nil, b, io.ErrUnexpectedEOF
+			case b[0] == ',':
+				b = b[1:]
+			default:
+				return nil, b, errors.New("expected ',' character in JSON array")
+			}
+		}
+
+		var v jsonValue
+		v, b, err = decodeValue(b)
+		if err != nil {
+			return nil, b, err
+		}
+
+		arr = append(arr, v)
+		init = true
+	}
+}
+
+// decodeString unmarshals the next JSON string per RFC 7159, section 7,
+// with special attention paid to RFC 7493, section 2.1
+// regarding rejection of unpaired surrogate halves.
+// It consume the leading value, and returns the remaining bytes.
+func decodeString(b []byte) (string, []byte, error) {
+	// indexNeedEscape returns the index of the character that needs escaping.
+	indexNeedEscape := func(b []byte) int {
+		for i, r := range string(b) {
+			if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
+				return i
+			}
+		}
+		return len(b)
+	}
+
+	b = trimSpace(b)
+	switch {
+	case len(b) == 0:
+		return "", b, io.ErrUnexpectedEOF
+	case b[0] == '"':
+		b = b[1:]
+	default:
+		return "", b, errors.New("expected '\"' character in JSON string")
+	}
+
+	var s []byte
+	i := indexNeedEscape(b)
+	b, s = b[i:], append(s, b[:i]...)
+	for len(b) > 0 {
+		switch r, n := utf8.DecodeRune(b); {
+		case r == utf8.RuneError && n == 1:
+			return "", b, errors.New("invalid UTF-8 in JSON string")
+		case r < ' ':
+			return "", b, fmt.Errorf("invalid character %q in string", r)
+		case r == '"':
+			b = b[1:]
+			return string(s), b, nil
+		case r == '\\':
+			if len(b) < 2 {
+				return "", b, io.ErrUnexpectedEOF
+			}
+			switch r := b[1]; r {
+			case '"', '\\', '/':
+				b, s = b[2:], append(s, r)
+			case 'b':
+				b, s = b[2:], append(s, '\b')
+			case 'f':
+				b, s = b[2:], append(s, '\f')
+			case 'n':
+				b, s = b[2:], append(s, '\n')
+			case 'r':
+				b, s = b[2:], append(s, '\r')
+			case 't':
+				b, s = b[2:], append(s, '\t')
+			case 'u':
+				if len(b) < 6 {
+					return "", b, io.ErrUnexpectedEOF
+				}
+				v, err := strconv.ParseUint(string(b[2:6]), 16, 16)
+				if err != nil {
+					return "", b, fmt.Errorf("invalid escape code %q in string", b[:6])
+				}
+				b = b[6:]
+
+				r := rune(v)
+				if utf16.IsSurrogate(r) {
+					if len(b) < 6 {
+						return "", b, io.ErrUnexpectedEOF
+					}
+					v, err := strconv.ParseUint(string(b[2:6]), 16, 16)
+					r = utf16.DecodeRune(r, rune(v))
+					if b[0] != '\\' || b[1] != 'u' || r == unicode.ReplacementChar || err != nil {
+						return "", b, fmt.Errorf("invalid escape code %q in string", b[:6])
+					}
+					b = b[6:]
+				}
+				s = append(s, string(r)...)
+			default:
+				return "", b, fmt.Errorf("invalid escape code %q in string", b[:2])
+			}
+		default:
+			i := indexNeedEscape(b[n:])
+			b, s = b[n+i:], append(s, b[:n+i]...)
+		}
+	}
+	return "", b, io.ErrUnexpectedEOF
+}
+
+// decodeNumber unmarshals the next JSON number per RFC 7159, section 6.
+// It consume the leading value, and returns the remaining bytes.
+func decodeNumber(b []byte) (float64, []byte, error) {
+	b = trimSpace(b)
+	b0 := b
+	if len(b) > 0 && b[0] == '-' {
+		b = b[1:]
+	}
+	switch {
+	case len(b) == 0:
+		return 0, b, io.ErrUnexpectedEOF
+	case b[0] == '0':
+		b = b[1:]
+	case '1' <= b[0] && b[0] <= '9':
+		b = b[1:]
+		for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+			b = b[1:]
+		}
+	default:
+		return 0, nil, errors.New("expected digit character in JSON number")
+	}
+	if len(b) > 0 && b[0] == '.' {
+		b = b[1:]
+		switch {
+		case len(b) == 0:
+			return 0, b, io.ErrUnexpectedEOF
+		case '0' <= b[0] && b[0] <= '9':
+			b = b[1:]
+		default:
+			return 0, nil, errors.New("expected digit character in JSON number")
+		}
+		for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+			b = b[1:]
+		}
+	}
+	if len(b) > 0 && (b[0] == 'e' || b[0] == 'E') {
+		b = b[1:]
+		if len(b) > 0 && (b[0] == '-' || b[0] == '+') {
+			b = b[1:]
+		}
+		switch {
+		case len(b) == 0:
+			return 0, b, io.ErrUnexpectedEOF
+		case '0' <= b[0] && b[0] <= '9':
+			b = b[1:]
+		default:
+			return 0, nil, errors.New("expected digit character in JSON number")
+		}
+		for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+			b = b[1:]
+		}
+	}
+
+	f, err := strconv.ParseFloat(string(b0[:len(b0)-len(b)]), 64)
+	if err != nil {
+		return 0, b, fmt.Errorf("invalid JSON number: %s", b0[:len(b0)-len(b)])
+	}
+	return f, b, nil
+}
+
+// trimSpace strips leading whitespace per RFC 7159, section 2.
+func trimSpace(b []byte) []byte {
+	return bytes.TrimLeft(b, " \t\n\r")
+}
diff --git a/jsoncs/float.go b/jsoncs/float.go
new file mode 100644
index 0000000..7bd7e87
--- /dev/null
+++ b/jsoncs/float.go
@@ -0,0 +1,140 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"bytes"
+	"math"
+	"strconv"
+)
+
+// BUG(https://golang.org/issue/29491): On Go 1.12 and earlier, float formatting
+// produces incorrect results in some rare cases due to a rounding error.
+
+// formatES6 appends a canonically formatted float according to ES6,
+// formally defined in ECMA-262, 6th edition, section 7.1.12.1.
+// This implements the formatting specified in RFC 8785, section 3.2.2.3,
+// except that this handles NaNs and Infinity.
+func formatES6(b []byte, m float64) []byte {
+	switch {
+	// 1. If m is NaN, return the String "NaN".
+	case math.IsNaN(m):
+		return append(b, "NaN"...)
+	// 2. If m is +0 or -0, return the String "0".
+	case m == 0:
+		return append(b, "0"...)
+	// 3. If m is less than zero, return the String concatenation of the String "-" and ToString(-m).
+	case m < 0:
+		return formatES6(append(b, '-'), -m)
+	// 4. If m is +∞, return the String "Infinity".
+	case math.IsInf(m, +1):
+		return append(b, "Infinity"...)
+	}
+
+	// 5. Otherwise, let n, k, and s be integers such that k ≥ 1, 10^(k-1) ≤ s < 10^k,
+	// the Number value for s ⨯ 10^(n-k) is m, and k is as small as possible.
+	// If there are multiple possibilities for s, choose the value of s for which s ⨯ 10^(n-k)
+	// is closest in value to m. If there are two such possible values of s, choose the one that is even.
+	// Note that k is the number of digits in the decimal representation of s
+	// and that s is not divisible by 10.
+	var n, k int64
+	var s []byte // decimal representation of s
+	{
+		// Unfortunately strconv.FormatFloat does not directly expose n, k, s,
+		// nor is the output defined as stable in any way.
+		// However, it's implementation is guaranteed to produce precise n, k, s values.
+		// Format a float with the 'e' format and derive n, k, s from the output.
+		var arr [32]byte
+		b := strconv.AppendFloat(arr[:0], m, 'e', -1, 64) // e.g., "d.dddde±dd"
+
+		// Parse the exponent.
+		i := bytes.IndexByte(b, 'e')
+		nk, err := strconv.ParseInt(string(b[i+len("e"):]), 10, 64) // i.e., n-k
+		if err != nil {
+			panic("BUG: unexpected strconv.ParseInt error: " + err.Error())
+		}
+
+		// Format the significand.
+		s = b[:i]
+		if len(b) > 1 && b[1] == '.' {
+			s[1], s = s[0], s[1:] // e.g., "d.dddd" => "ddddd"
+			for len(s) > 1 && s[len(s)-1] == '0' {
+				s = s[:len(s)-1] // trim trailing zeros
+			}
+			nk -= int64(len(s) - 1)
+		}
+
+		k = int64(len(s)) // k is the number of digits in the decimal representation of s
+		n = nk + k        // nk=n-k => n=nk+k
+	}
+
+	const zeros = "000000000000000000000"
+	switch {
+	// 6. If k ≤ n ≤ 21, …
+	case k <= n && n <= 21:
+		// … return the String consisting of the code units of the k digits of
+		//   the decimal representation of s (in order, with no leading zeros), …
+		b = append(b, s...)
+		// … followed by n-k occurrences of the code unit 0x0030 (DIGIT ZERO).
+		b = append(b, zeros[:n-k]...)
+
+	// 7. If 0 < n ≤ 21, …
+	case 0 < n && n <= 21:
+		// … return the String consisting of the code units of the
+		//   most significant n digits of the decimal representation of s, …
+		b = append(b, s[:n]...)
+		// … followed by the code unit 0x002E (FULL STOP), …
+		b = append(b, '.')
+		// … followed by the code units of the remaining k-n digits of the decimal representation of s.
+		b = append(b, s[n:]...)
+
+	// 8. If -6 < n ≤ 0, …
+	case -6 < n && n <= 0:
+		// … return the String consisting of the code unit 0x0030 (DIGIT ZERO), …
+		b = append(b, '0')
+		// … followed by the code unit 0x002E (FULL STOP), …
+		b = append(b, '.')
+		// … followed by -n occurrences of the code unit 0x0030 (DIGIT ZERO), …
+		b = append(b, zeros[:-n]...)
+		// … followed by the code units of the k digits of the decimal representation of s.
+		b = append(b, s...)
+
+	// 9. If k = 1, …
+	case k == 1:
+		// … return the String consisting of the code unit of the single digit of s, …
+		b = append(b, s...)
+		// … followed by code unit 0x0065 (LATIN SMALL LETTER E), …
+		b = append(b, 'e')
+		// … followed by code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative, …
+		// … followed by the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes).
+		switch {
+		case n-1 > 0:
+			b = strconv.AppendInt(append(b, '+'), n-1, 10)
+		case n-1 < 0:
+			b = strconv.AppendInt(append(b, '-'), 1-n, 10)
+		}
+
+	// 10. Otherwise …
+	default:
+		// … return the String consisting of the code units of the
+		//   most significant digit of the decimal representation of s, …
+		b = append(b, s[0])
+		// … followed by code unit 0x002E (FULL STOP), …
+		b = append(b, '.')
+		// … followed by the code units of the remaining k-1 digits of the decimal representation of s, …
+		b = append(b, s[1:]...)
+		// … followed by code unit 0x0065 (LATIN SMALL LETTER E), …
+		b = append(b, 'e')
+		// … followed by code unit 0x002B (PLUS SIGN) or the code unit 0x002D (HYPHEN-MINUS) according to whether n-1 is positive or negative, …
+		// … followed by the code units of the decimal representation of the integer abs(n-1) (with no leading zeroes).
+		switch {
+		case n-1 > 0:
+			b = strconv.AppendInt(append(b, '+'), n-1, 10)
+		case n-1 < 0:
+			b = strconv.AppendInt(append(b, '-'), 1-n, 10)
+		}
+	}
+	return b
+}
diff --git a/jsoncs/float_test.go b/jsoncs/float_test.go
new file mode 100644
index 0000000..bf22bb9
--- /dev/null
+++ b/jsoncs/float_test.go
@@ -0,0 +1,58 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"math"
+	"testing"
+)
+
+func TestFormatES6(t *testing.T) {
+	tests := []struct {
+		in   float64
+		want string
+	}{
+		{math.NaN(), "NaN"},
+		{math.Inf(-1), "-Infinity"},
+		{math.Inf(+1), "Infinity"},
+		{math.Copysign(0, -1), "0"},
+		{math.Copysign(0, +1), "0"},
+
+		// The following cases are from RFC 8785, Appendix B.
+		{math.Float64frombits(0x0000000000000000), "0"},
+		{math.Float64frombits(0x8000000000000000), "0"},
+		{math.Float64frombits(0x0000000000000001), "5e-324"},
+		{math.Float64frombits(0x8000000000000001), "-5e-324"},
+		{math.Float64frombits(0x7fefffffffffffff), "1.7976931348623157e+308"},
+		{math.Float64frombits(0xffefffffffffffff), "-1.7976931348623157e+308"},
+		{math.Float64frombits(0x4340000000000000), "9007199254740992"},
+		{math.Float64frombits(0xc340000000000000), "-9007199254740992"},
+		{math.Float64frombits(0x4430000000000000), "295147905179352830000"},
+		{math.Float64frombits(0x7fffffffffffffff), "NaN"},
+		{math.Float64frombits(0x7ff0000000000000), "Infinity"},
+		{math.Float64frombits(0x44b52d02c7e14af5), "9.999999999999997e+22"},
+		{math.Float64frombits(0x44b52d02c7e14af6), "1e+23"},
+		{math.Float64frombits(0x44b52d02c7e14af7), "1.0000000000000001e+23"},
+		{math.Float64frombits(0x444b1ae4d6e2ef4e), "999999999999999700000"},
+		{math.Float64frombits(0x444b1ae4d6e2ef4f), "999999999999999900000"},
+		{math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21"},
+		{math.Float64frombits(0x3eb0c6f7a0b5ed8c), "9.999999999999997e-7"},
+		{math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001"},
+		{math.Float64frombits(0x41b3de4355555553), "333333333.3333332"},
+		{math.Float64frombits(0x41b3de4355555554), "333333333.33333325"},
+		{math.Float64frombits(0x41b3de4355555555), "333333333.3333333"},
+		{math.Float64frombits(0x41b3de4355555556), "333333333.3333334"},
+		{math.Float64frombits(0x41b3de4355555557), "333333333.33333343"},
+		{math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333333333333"},
+		{math.Float64frombits(0x43143ff3c1cb0959), "1424953923781206.2"},
+	}
+
+	for _, tt := range tests {
+		got := string(formatES6(nil, tt.in))
+		if got != tt.want {
+			t.Errorf("formatES6(%016x) = %v, want %v", math.Float64bits(tt.in), got, tt.want)
+		}
+	}
+}
diff --git a/jsoncs/format.go b/jsoncs/format.go
new file mode 100644
index 0000000..9cbeb90
--- /dev/null
+++ b/jsoncs/format.go
@@ -0,0 +1,158 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+// Package jsoncs implements of the JSON Canonicalization Scheme (JCS)
+// as specified in RFC 8785.
+package jsoncs
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"math"
+	"math/bits"
+	"sort"
+	"strconv"
+	"unicode/utf8"
+)
+
+// Format transforms the JSON input to its canonical form.
+// The input must comply with RFC 7493.
+// It reuses the provided input buffer.
+func Format(b []byte) ([]byte, error) {
+	if Valid(b) {
+		return b, nil
+	}
+	v, err := decode(b)
+	if err != nil {
+		return nil, err
+	}
+	return formatValue(b[:0], v)
+}
+
+// formatValue canonically marshals a JSON value.
+func formatValue(b []byte, v jsonValue) ([]byte, error) {
+	switch v := v.(type) {
+	case jsonObject:
+		return formatObject(b, v)
+	case jsonArray:
+		return formatArray(b, v)
+	case string:
+		return formatString(b, v)
+	case float64:
+		return formatNumber(b, v)
+	case nil:
+		return append(b, nullLiteral...), nil
+	case bool:
+		switch v {
+		case true:
+			return append(b, trueLiteral...), nil
+		case false:
+			return append(b, falseLiteral...), nil
+		}
+	}
+	return nil, fmt.Errorf("invalid type: %T", v)
+}
+
+// formatObject canonically marshals a JSON object per RFC 8785, section 3.2.3.
+func formatObject(b []byte, obj jsonObject) ([]byte, error) {
+	var ks []string
+	for k := range obj {
+		ks = append(ks, k)
+	}
+	sort.Slice(ks, func(i, j int) bool {
+		return lessUTF16(ks[i], ks[j])
+	})
+
+	var err error
+	b = append(b, '{')
+	for _, k := range ks {
+		b, err = formatString(b, k)
+		if err != nil {
+			return nil, err
+		}
+		b = append(b, ':')
+		b, err = formatValue(b, obj[k])
+		if err != nil {
+			return nil, err
+		}
+		b = append(b, ',')
+	}
+	b = bytes.TrimRight(b, ",")
+	b = append(b, '}')
+	return b, nil
+}
+
+// formatArray canonically marshals a JSON array.
+func formatArray(b []byte, arr jsonArray) ([]byte, error) {
+	var err error
+	b = append(b, '[')
+	for _, v := range arr {
+		b, err = formatValue(b, v)
+		if err != nil {
+			return nil, err
+		}
+		b = append(b, ',')
+	}
+	b = bytes.TrimRight(b, ",")
+	b = append(b, ']')
+	return b, nil
+}
+
+// formatArray canonically marshals a JSON string per RFC 8785, section 3.2.2.2.
+func formatString(b []byte, s string) ([]byte, error) {
+	// indexNeedEscape returns the index of the character that needs escaping.
+	indexNeedEscape := func(s string) int {
+		for i, r := range s {
+			if r < ' ' || r == '\\' || r == '"' || r == utf8.RuneError {
+				return i
+			}
+		}
+		return len(s)
+	}
+
+	b = append(b, '"')
+	i := indexNeedEscape(s)
+	s, b = s[i:], append(b, s[:i]...)
+	for len(s) > 0 {
+		switch r, n := utf8.DecodeRuneInString(s); {
+		case r == utf8.RuneError && n == 1:
+			return nil, errors.New("invalid UTF-8 in string")
+		case r < ' ' || r == '"' || r == '\\':
+			b = append(b, '\\')
+			switch r {
+			case '"', '\\':
+				b = append(b, byte(r))
+			case '\b':
+				b = append(b, 'b')
+			case '\f':
+				b = append(b, 'f')
+			case '\n':
+				b = append(b, 'n')
+			case '\r':
+				b = append(b, 'r')
+			case '\t':
+				b = append(b, 't')
+			default:
+				b = append(b, 'u')
+				b = append(b, "0000"[1+(bits.Len32(uint32(r))-1)/4:]...)
+				b = strconv.AppendUint(b, uint64(r), 16)
+			}
+			s = s[n:]
+		default:
+			i := indexNeedEscape(s[n:])
+			s, b = s[n+i:], append(b, s[:n+i]...)
+		}
+	}
+	b = append(b, '"')
+	return b, nil
+}
+
+// formatNumber canonically marshals a JSON number per RFC 8785, section 3.2.2.3.
+func formatNumber(b []byte, f float64) ([]byte, error) {
+	if math.IsNaN(f) || math.IsInf(f, 0) {
+		return nil, fmt.Errorf("invalid float value: %v", f)
+	}
+	return formatES6(b, f), nil
+}
diff --git a/jsoncs/format_test.go b/jsoncs/format_test.go
new file mode 100644
index 0000000..03f7309
--- /dev/null
+++ b/jsoncs/format_test.go
@@ -0,0 +1,485 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"io"
+	"strings"
+	"testing"
+	"unicode/utf8"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
+)
+
+type containsError string
+
+func (e containsError) Error() string {
+	return string(e)
+}
+func (e containsError) Is(e2 error) bool {
+	if e2 == nil {
+		return false
+	}
+	return strings.Contains(e2.Error(), string(e))
+}
+
+func TestFormat(t *testing.T) {
+	tests := []struct {
+		inJSON   string
+		wantJSON string
+		wantErr  error
+	}{{
+		inJSON:  "",
+		wantErr: io.ErrUnexpectedEOF,
+	}, {
+		inJSON:  "?",
+		wantErr: containsError("expected next JSON value"),
+	}, {
+		inJSON:   "null",
+		wantJSON: "null",
+	}, {
+		inJSON:   " \n\r\tnull \n\r\t",
+		wantJSON: "null",
+	}, {
+		inJSON:  "nullnull",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:   "true",
+		wantJSON: "true",
+	}, {
+		inJSON:   " \n\r\ttrue \n\r\t",
+		wantJSON: "true",
+	}, {
+		inJSON:  "truetrue",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:   "false",
+		wantJSON: "false",
+	}, {
+		inJSON:   " \n\r\tfalse \n\r\t",
+		wantJSON: "false",
+	}, {
+		inJSON:  "falsefalse",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:   "0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "1",
+		wantJSON: "1",
+	}, {
+		inJSON:   "-1",
+		wantJSON: "-1",
+	}, {
+		inJSON:   "0.1",
+		wantJSON: "0.1",
+	}, {
+		inJSON:   "-0.1",
+		wantJSON: "-0.1",
+	}, {
+		inJSON:   "1234",
+		wantJSON: "1234",
+	}, {
+		inJSON:   "-1234",
+		wantJSON: "-1234",
+	}, {
+		inJSON:   "12.34",
+		wantJSON: "12.34",
+	}, {
+		inJSON:   "-12.34",
+		wantJSON: "-12.34",
+	}, {
+		inJSON:   "12E0",
+		wantJSON: "12",
+	}, {
+		inJSON:   "12E1",
+		wantJSON: "120",
+	}, {
+		inJSON:   "12e34",
+		wantJSON: "1.2e+35",
+	}, {
+		inJSON:   "12E-0",
+		wantJSON: "12",
+	}, {
+		inJSON:   "12e+1",
+		wantJSON: "120",
+	}, {
+		inJSON:   "12e-34",
+		wantJSON: "1.2e-33",
+	}, {
+		inJSON:   "-12E0",
+		wantJSON: "-12",
+	}, {
+		inJSON:   "-12E1",
+		wantJSON: "-120",
+	}, {
+		inJSON:   "-12e34",
+		wantJSON: "-1.2e+35",
+	}, {
+		inJSON:   "-12E-0",
+		wantJSON: "-12",
+	}, {
+		inJSON:   "-12e+1",
+		wantJSON: "-120",
+	}, {
+		inJSON:   "-12e-34",
+		wantJSON: "-1.2e-33",
+	}, {
+		inJSON:   "1.2E0",
+		wantJSON: "1.2",
+	}, {
+		inJSON:   "1.2E1",
+		wantJSON: "12",
+	}, {
+		inJSON:   "1.2e34",
+		wantJSON: "1.2e+34",
+	}, {
+		inJSON:   "1.2E-0",
+		wantJSON: "1.2",
+	}, {
+		inJSON:   "1.2e+1",
+		wantJSON: "12",
+	}, {
+		inJSON:   "1.2e-34",
+		wantJSON: "1.2e-34",
+	}, {
+		inJSON:   "-1.2E0",
+		wantJSON: "-1.2",
+	}, {
+		inJSON:   "-1.2E1",
+		wantJSON: "-12",
+	}, {
+		inJSON:   "-1.2e34",
+		wantJSON: "-1.2e+34",
+	}, {
+		inJSON:   "-1.2E-0",
+		wantJSON: "-1.2",
+	}, {
+		inJSON:   "-1.2e+1",
+		wantJSON: "-12",
+	}, {
+		inJSON:   "-1.2e-34",
+		wantJSON: "-1.2e-34",
+	}, {
+		inJSON:   "0E0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0E1",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0e34",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0E-0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0e+1",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0e-34",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0E0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0E1",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0e34",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0E-0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0e+1",
+		wantJSON: "0",
+	}, {
+		inJSON:   "-0e-34",
+		wantJSON: "0",
+	}, {
+		inJSON:   "12345678901234567890",
+		wantJSON: "12345678901234567000",
+	}, {
+		inJSON:   "-123456789.0123456789e+0123",
+		wantJSON: "-1.2345678901234568e+131",
+	}, {
+		inJSON:   " \n\r\t-123456789.0123456789e+0123 \n\r\t",
+		wantJSON: "-1.2345678901234568e+131",
+	}, {
+		inJSON:  "- 123456789.0123456789e+0123",
+		wantErr: containsError("expected digit character in JSON number"),
+	}, {
+		inJSON:  "-123456789 .0123456789e+0123",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:  "-123456789. 0123456789e+0123",
+		wantErr: containsError("expected digit character in JSON number"),
+	}, {
+		inJSON:  "-123456789.0123456789 e+0123",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:  "-123456789.0123456789e +0123",
+		wantErr: containsError("expected digit character in JSON number"),
+	}, {
+		inJSON:  "-123456789.0123456789e+ 0123",
+		wantErr: containsError("expected digit character in JSON number"),
+	}, {
+		inJSON:  "+0",
+		wantErr: containsError("expected next JSON value"),
+	}, {
+		inJSON:  "00",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:  "01",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:  "0f",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:  "0.e0",
+		wantErr: containsError("expected digit character in JSON number"),
+	}, {
+		inJSON:   "0.0E0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0.0E+0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0.0E-0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0.0E+0",
+		wantJSON: "0",
+	}, {
+		inJSON:   "0.0E-0",
+		wantJSON: "0",
+	}, {
+		inJSON:  "0.0E0.",
+		wantErr: containsError("invalid data"),
+	}, {
+		inJSON:   `""`,
+		wantJSON: `""`,
+	}, {
+		inJSON:   " \n\r\t\"\" \n\r\t",
+		wantJSON: `""`,
+	}, {
+		inJSON:   " \n\r\t\"hello\" \n\r\t",
+		wantJSON: `"hello"`,
+	}, {
+		inJSON:  "\"\x00\"",
+		wantErr: containsError("invalid character"),
+	}, {
+		inJSON:  "\"\xff\"",
+		wantErr: containsError("invalid UTF-8"),
+	}, {
+		inJSON:   `"` + string(utf8.RuneError) + `"`,
+		wantJSON: `"` + string(utf8.RuneError) + `"`,
+	}, {
+		inJSON:   `"\uFFFD"`,
+		wantJSON: "\"\uFFFD\"",
+	}, {
+		inJSON:  `"\x"`,
+		wantErr: containsError("invalid escape code"),
+	}, {
+		inJSON:  `"\uXXXX"`,
+		wantErr: containsError("invalid escape code"),
+	}, {
+		inJSON:  `"\uDEAD"`, // unmatched surrogate pair
+		wantErr: io.ErrUnexpectedEOF,
+	}, {
+		inJSON:  `"\uDEAD______"`, // unmatched surrogate pair
+		wantErr: containsError("invalid escape code"),
+	}, {
+		inJSON:  `"\uDEAD\uBEEF"`, // invalid surrogate half
+		wantErr: containsError("invalid escape code"),
+	}, {
+		inJSON:   `"\uD800\udead"`, // valid surrogate pair
+		wantJSON: `"𐊭"`,
+	}, {
+		inJSON:   `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f"`,
+		wantJSON: `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f"`,
+	}, {
+		inJSON:   `"\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"`,
+		wantJSON: `"\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f"`,
+	}, {
+		inJSON:   `"\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002a\u002b\u002c\u002d\u002e\u002f"`,
+		wantJSON: `" !\"#$%&'()*+,-./"`,
+	}, {
+		inJSON:   `"\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003a\u003b\u003c\u003d\u003e\u003f"`,
+		wantJSON: `"0123456789:;<=>?"`,
+	}, {
+		inJSON:   `"\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f"`,
+		wantJSON: `"@ABCDEFGHIJKLMNO"`,
+	}, {
+		inJSON:   `"\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u005b\u005c\u005d\u005e\u005f"`,
+		wantJSON: `"PQRSTUVWXYZ[\\]^_"`,
+	}, {
+		inJSON:   `"\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f"`,
+		wantJSON: "\"`abcdefghijklmno\"",
+	}, {
+		inJSON:   `"\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u007b\u007c\u007d\u007e\u007f"`,
+		wantJSON: "\"pqrstuvwxyz{|}~\u007f\"",
+	}, {
+		inJSON:   `"\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f"`,
+		wantJSON: "\"\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f\"",
+	}, {
+		inJSON:   `"\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f"`,
+		wantJSON: "\"\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f\"",
+	}, {
+		inJSON:   `"\u00a0\u00a1\u00a2\u00a3\u00a4\u00a5\u00a6\u00a7\u00a8\u00a9\u00aa\u00ab\u00ac\u00ad\u00ae\u00af"`,
+		wantJSON: "\"\u00a0¡¢£¤¥¦§¨©ª«¬\u00ad®¯\"",
+	}, {
+		inJSON:   `"\u00b0\u00b1\u00b2\u00b3\u00b4\u00b5\u00b6\u00b7\u00b8\u00b9\u00ba\u00bb\u00bc\u00bd\u00be\u00bf"`,
+		wantJSON: `"°±²³´µ¶·¸¹º»¼½¾¿"`,
+	}, {
+		inJSON:   `"\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf"`,
+		wantJSON: `"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ"`,
+	}, {
+		inJSON:   `"\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d7\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u00df"`,
+		wantJSON: `"ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß"`,
+	}, {
+		inJSON:   `"\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef"`,
+		wantJSON: `"àáâãäåæçèéêëìíîï"`,
+	}, {
+		inJSON:   `"\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f7\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00fe\u00ff"`,
+		wantJSON: `"ðñòóôõö÷øùúûüýþÿ"`,
+	}, {
+		inJSON:   `"בְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ׃"`,
+		wantJSON: `"בְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ׃"`,
+	}, {
+		inJSON:   `"ἐν ἀρχῇ ἐποίησεν ὁ θεὸς τὸν οὐρανὸν καὶ τὴν γῆν"`,
+		wantJSON: `"ἐν ἀρχῇ ἐποίησεν ὁ θεὸς τὸν οὐρανὸν καὶ τὴν γῆν"`,
+	}, {
+		inJSON:   `"في البدء خلق الله السموات والارض."`,
+		wantJSON: `"في البدء خلق الله السموات والارض."`,
+	}, {
+		inJSON:   `"起 初 ,   神 创 造 天 地 。"`,
+		wantJSON: `"起 初 ,   神 创 造 天 地 。"`,
+	}, {
+		inJSON:   `"ในเริ่มแรกนั้นพระเจ้าทรงเนรมิตสร้างฟ้าและแผ่นดินโลก"`,
+		wantJSON: `"ในเริ่มแรกนั้นพระเจ้าทรงเนรมิตสร้างฟ้าและแผ่นดินโลก"`,
+	}, {
+		inJSON:   "[]",
+		wantJSON: "[]",
+	}, {
+		inJSON:   " \n\r\t[ \n\r\t] \n\r\t",
+		wantJSON: "[]",
+	}, {
+		inJSON:   " \n\r\t[ \n\r\t0 \n\r\t] \n\r\t",
+		wantJSON: "[0]",
+	}, {
+		inJSON:   " \n\r\t[ \n\r\t0 \n\r\t, \n\r\t1 \n\r\t] \n\r\t",
+		wantJSON: "[0,1]",
+	}, {
+		inJSON:  "[0{}]",
+		wantErr: containsError("expected ',' character in JSON array"),
+	}, {
+		inJSON:  "[][]",
+		wantErr: containsError("invalid data after top-level value"),
+	}, {
+		inJSON:   "{}",
+		wantJSON: "{}",
+	}, {
+		inJSON:   " \n\r\t{ \n\r\t} \n\r\t",
+		wantJSON: "{}",
+	}, {
+		inJSON:   " \n\r\t{ \n\r\t\"0\" \n\r\t: \n\r\t0 \n\r\t} \n\r\t",
+		wantJSON: `{"0":0}`,
+	}, {
+		inJSON:   " \n\r\t{ \n\r\t\"0\" \n\r\t: \n\r\t0 \n\r\t, \n\r\t\"1\" \n\r\t: \n\r\t1 \n\r\t} \n\r\t",
+		wantJSON: `{"0":0,"1":1}`,
+	}, {
+		inJSON:  `{"k"}`,
+		wantErr: containsError("expected ':' character in JSON object"),
+	}, {
+		inJSON:  `{"k":0,}`,
+		wantErr: containsError("expected '\"' character in JSON string"),
+	}, {
+		inJSON:  `{"k":0[]}`,
+		wantErr: containsError("expected ',' character in JSON object"),
+	}, {
+		inJSON:  `{"k":0,"\u006b":0}`,
+		wantErr: containsError("duplicate key \"k\" in JSON object"),
+	}, {
+		inJSON:  "{}{}",
+		wantErr: containsError("invalid data after top-level value"),
+	}, {
+		inJSON: `[
+			56,
+			{
+				"d": true,
+				"10": null,
+				"1": [ ]
+			}
+		]`,
+		wantJSON: `[56,{"1":[],"10":null,"d":true}]`,
+	}, {
+		inJSON: `{
+			"peach": "This sorting order",
+			"péché": "is wrong according to French",
+			"pêche": "but canonicalization MUST",
+			"sin":   "ignore locale"
+		}`,
+		wantJSON: `{"peach":"This sorting order","péché":"is wrong according to French","pêche":"but canonicalization MUST","sin":"ignore locale"}`,
+	}, {
+		inJSON: `{
+			"1": {"f": {"f": "hi","F": 5} ,"\n": 56.0},
+			"10": { },
+			"": "empty",
+			"a": { },
+			"111": [ {"e": "yes","E": "no" } ],
+			"A": { }
+		}`,
+		wantJSON: `{"":"empty","1":{"\n":56,"f":{"F":5,"f":"hi"}},"10":{},"111":[{"E":"no","e":"yes"}],"A":{},"a":{}}`,
+	}, {
+		inJSON: `{
+			"Unnormalized Unicode":"A\u030a"
+		}`,
+		wantJSON: `{"Unnormalized Unicode":"Å"}`,
+	}, {
+		inJSON: `{
+			"numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001],
+			"string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
+			"literals": [null, true, false]
+		}`,
+		wantJSON: `{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}`,
+	}, {
+		inJSON: `{
+			"\u20ac": "Euro Sign",
+			"\r": "Carriage Return",
+			"\u000a": "Newline",
+			"1": "One",
+			"\u0080": "Control\u007f",
+			"\ud83d\ude02": "Smiley",
+			"\u00f6": "Latin Small Letter O With Diaeresis",
+			"\ufb33": "Hebrew Letter Dalet With Dagesh",
+			"</script>": "Browser Challenge"
+		}`,
+		wantJSON: `{"\n":"Newline","\r":"Carriage Return","1":"One","</script>":"Browser Challenge","` + "\u0080" + `":"Control` + "\u007f" + `","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😂":"Smiley","דּ":"Hebrew Letter Dalet With Dagesh"}`,
+	}}
+
+	for _, tt := range tests {
+		t.Run("", func(t *testing.T) {
+			t.Logf("Input Data: %q", tt.inJSON)
+			gotJSON, gotErr := Format([]byte(tt.inJSON))
+			if diff := cmp.Diff(string(tt.wantJSON), string(gotJSON)); diff != "" {
+				t.Errorf("Format output mismatch (-want +got):\n%s", diff)
+			}
+			if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
+				t.Errorf("Format output mismatch (-want +got):\n%s", diff)
+			}
+
+			gotValid := Valid([]byte(tt.inJSON))
+			wantValid := string(tt.inJSON) == string(gotJSON) && gotErr == nil
+			if gotValid != wantValid {
+				t.Errorf("Valid() = %v, want %v", gotValid, wantValid)
+			}
+		})
+	}
+}
diff --git a/jsoncs/go.mod b/jsoncs/go.mod
new file mode 100644
index 0000000..75f5934
--- /dev/null
+++ b/jsoncs/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/jsoncs
+
+go 1.9
+
+require github.com/google/go-cmp v0.5.1
diff --git a/jsoncs/go.sum b/jsoncs/go.sum
new file mode 100644
index 0000000..6295580
--- /dev/null
+++ b/jsoncs/go.sum
@@ -0,0 +1,4 @@
+github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/jsoncs/string.go b/jsoncs/string.go
new file mode 100644
index 0000000..45c3b7a
--- /dev/null
+++ b/jsoncs/string.go
@@ -0,0 +1,66 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"unicode/utf16"
+	"unicode/utf8"
+)
+
+// lessUTF16 reports whether x is lexicographically less than y according
+// to the UTF-16 codepoints of the UTF-8 encoded input strings.
+// This implements the ordering specified in RFC 8785, section 3.2.3.
+func lessUTF16(x, y string) bool {
+	for {
+		if len(x) == 0 || len(y) == 0 {
+			return len(x) < len(y)
+		}
+
+		// ASCII fast-path.
+		if x[0] < utf8.RuneSelf || y[0] < utf8.RuneSelf {
+			if x[0] != y[0] {
+				return x[0] < y[0]
+			}
+			x, y = x[1:], y[1:]
+			continue
+		}
+
+		// Decode next pair of runes as UTF-8.
+		rx, nx := utf8.DecodeRuneInString(x)
+		ry, ny := utf8.DecodeRuneInString(y)
+		switch {
+
+		// Both runes encode as either a single or surrogate pair
+		// of UTF-16 codepoints.
+		case isUTF16Self(rx) == isUTF16Self(ry):
+			if rx != ry {
+				return rx < ry
+			}
+
+		// The x rune is a single UTF-16 codepoint, while
+		// the y rune is a surrogate pair of UTF-16 codepoints.
+		case isUTF16Self(rx):
+			ry, _ := utf16.EncodeRune(ry)
+			if rx != ry {
+				return rx < ry
+			}
+			panic("invalid UTF-8") // implies rx is an unpaired surrogate half
+
+		// The y rune is a single UTF-16 codepoint, while
+		// the x rune is a surrogate pair of UTF-16 codepoints.
+		case isUTF16Self(ry):
+			rx, _ := utf16.EncodeRune(rx)
+			if rx != ry {
+				return rx < ry
+			}
+			panic("invalid UTF-8") // implies ry is an unpaired surrogate half
+		}
+		x, y = x[nx:], y[ny:]
+	}
+}
+
+func isUTF16Self(r rune) bool {
+	return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
+}
diff --git a/jsoncs/string_test.go b/jsoncs/string_test.go
new file mode 100644
index 0000000..1ea8cc5
--- /dev/null
+++ b/jsoncs/string_test.go
@@ -0,0 +1,54 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"math/rand"
+	"sort"
+	"testing"
+	"unicode/utf16"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestLessUTF16(t *testing.T) {
+	want := []string{"", "\r", "1", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"}
+
+	got1 := append([]string(nil), want...)
+	got2 := append([]string(nil), want...)
+	for i, j := range rand.Perm(len(want)) {
+		got1[i], got1[j] = got1[j], got1[i]
+		got2[i], got2[j] = got2[j], got2[i]
+	}
+
+	// Sort using optimized lessUTF16 implementation.
+	sort.Slice(got1, func(i, j int) bool {
+		return lessUTF16(got1[i], got1[j])
+	})
+	if diff := cmp.Diff(want, got1); diff != "" {
+		t.Errorf("sort.Slice(LessUTF16.Optimized) mismatch (-want +got)\n%s", diff)
+	}
+
+	// Sort using simple, but slow lessUTF16 implementation.
+	lessUTF16 := func(x, y string) bool {
+		ux := utf16.Encode([]rune(x))
+		uy := utf16.Encode([]rune(y))
+		for {
+			if len(ux) == 0 || len(uy) == 0 {
+				return len(ux) < len(uy)
+			}
+			if ux[0] != uy[0] {
+				return ux[0] < uy[0]
+			}
+			ux, uy = ux[1:], uy[1:]
+		}
+	}
+	sort.Slice(got2, func(i, j int) bool {
+		return lessUTF16(got2[i], got2[j])
+	})
+	if diff := cmp.Diff(want, got2); diff != "" {
+		t.Errorf("sort.Slice(LessUTF16.Simplified) mismatch (-want +got)\n%s", diff)
+	}
+}
diff --git a/jsoncs/valid.go b/jsoncs/valid.go
new file mode 100644
index 0000000..a88be37
--- /dev/null
+++ b/jsoncs/valid.go
@@ -0,0 +1,194 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE.md file.
+
+package jsoncs
+
+import (
+	"bytes"
+)
+
+// Valid reports whether the JSON input is in canonical form.
+// Invalid JSON input is reported as false.
+func Valid(b []byte) bool {
+	b, ok := validValue(b)
+	return ok && len(b) == 0
+}
+
+// validValue reports whether the next JSON value is in its canonical form.
+// It consume the leading value, and returns the remaining bytes.
+func validValue(b []byte) ([]byte, bool) {
+	switch {
+	case len(b) > 0 && b[0] == '{':
+		return validObject(b)
+	case len(b) > 0 && b[0] == '[':
+		return validArray(b)
+	case len(b) > 0 && b[0] == '"':
+		return validString(b)
+	case len(b) > 0 && (b[0] == '-' || ('0' <= b[0] && b[0] <= '9')):
+		return validNumber(b)
+	case bytes.HasPrefix(b, nullLiteral):
+		return b[len(nullLiteral):], true
+	case bytes.HasPrefix(b, trueLiteral):
+		return b[len(trueLiteral):], true
+	case bytes.HasPrefix(b, falseLiteral):
+		return b[len(falseLiteral):], true
+	default:
+		return b, false
+	}
+}
+
+// validObject reports whether the next JSON object is in its canonical form
+// per RFC 8785, section 3.2.3 regarding object name ordering.
+// It consume the leading value, and returns the remaining bytes.
+func validObject(b []byte) ([]byte, bool) {
+	if len(b) == 0 || b[0] != '{' {
+		return b, false
+	}
+	b = b[1:]
+
+	var init, ok bool
+	var prevKey string
+	for {
+		if len(b) > 0 && b[0] == '}' {
+			return b[1:], true
+		}
+
+		if init {
+			if len(b) == 0 || b[0] != ',' {
+				return b, false
+			}
+			b = b[1:]
+		}
+
+		currKey, _, _ := decodeString(b)
+		b, ok = validString(b)
+		if !ok {
+			return b, ok
+		}
+		if init && !lessUTF16(prevKey, currKey) {
+			return b, ok
+		}
+		prevKey = currKey
+
+		if len(b) == 0 || b[0] != ':' {
+			return b, false
+		}
+		b = b[1:]
+
+		b, ok = validValue(b)
+		if !ok {
+			return b, ok
+		}
+
+		init = true
+	}
+}
+
+// validArray reports whether the next JSON array is in its canonical form.
+// It consume the leading value, and returns the remaining bytes.
+func validArray(b []byte) ([]byte, bool) {
+	if len(b) == 0 || b[0] != '[' {
+		return b, false
+	}
+	b = b[1:]
+
+	var init, ok bool
+	for {
+		if len(b) > 0 && b[0] == ']' {
+			return b[1:], true
+		}
+
+		if init {
+			if len(b) == 0 || b[0] != ',' {
+				return b, false
+			}
+			b = b[1:]
+		}
+
+		b, ok = validValue(b)
+		if !ok {
+			return b, ok
+		}
+
+		init = true
+	}
+}
+
+// validString reports whether the next JSON string is in its canonical form
+// per RFC 8785, section 3.2.2.2.
+// It consume the leading value, and returns the remaining bytes.
+func validString(b []byte) ([]byte, bool) {
+	if len(b) == 0 || b[0] != '"' {
+		return b, false
+	}
+
+	// Fast-path optimization for unescaped ASCII.
+	for b := b[1:]; len(b) > 0; b = b[1:] {
+		if b[0] == '"' {
+			return b[1:], true
+		}
+		if !(0x20 <= b[0] && b[0] < 0x80 && b[0] != '"' && b[0] != '\\') {
+			break
+		}
+	}
+
+	s, b2, err := decodeString(b)
+	got := b[:len(b)-len(b2)]
+	want, _ := formatString(nil, s)
+	return b2, bytes.Equal(got, want) && err == nil
+}
+
+// validNumber reports whether the next JSON number is in its canonical form
+// per RFC 8785, section 3.2.2.3.
+// It consume the leading value, and returns the remaining bytes.
+func validNumber(b []byte) ([]byte, bool) {
+	if len(b) == 0 || !(b[0] == '-' || ('0' <= b[0] && b[0] <= '9')) {
+		return b, false
+	}
+
+	// Fast-path optimization for integers.
+	// Integer values in the range of ±2⁵³ are represented in decimal,
+	// which is encoded using up to 16 digits (excluding the sign).
+	{
+		b := b
+		var neg bool
+		if len(b) > 0 && b[0] == '-' {
+			b = b[1:]
+			neg = true
+		}
+		switch {
+		case len(b) == 0:
+			break
+		case b[0] == '0':
+			b = b[1:]
+			if neg {
+				break // -0 is not permitted
+			}
+			if len(b) > 0 && (b[0] == '.' || b[0] == 'e' || b[0] == 'E') {
+				break // number is not yet terminated
+			}
+			return b, true
+		case '1' <= b[0] && b[0] <= '9':
+			var n int
+			b = b[1:]
+			n++
+			for len(b) > 0 && ('0' <= b[0] && b[0] <= '9') {
+				b = b[1:]
+				n++
+			}
+			if n >= 16 {
+				break // possibly exceeds ±2⁵³
+			}
+			if len(b) > 0 && (b[0] == '.' || b[0] == 'e' || b[0] == 'E') {
+				break // number is not yet terminated
+			}
+			return b, true
+		}
+	}
+
+	f, b2, err := decodeNumber(b)
+	got := b[:len(b)-len(b2)]
+	want, _ := formatNumber(nil, f)
+	return b2, bytes.Equal(got, want) && err == nil
+}
diff --git a/jsonfmt/LICENSE.md b/jsonfmt/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/jsonfmt/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/jsonfmt/go.mod b/jsonfmt/go.mod
new file mode 100644
index 0000000..4fae97d
--- /dev/null
+++ b/jsonfmt/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/jsonfmt
+
+go 1.12
+
+require github.com/google/go-cmp v0.3.0
diff --git a/jsonfmt/go.sum b/jsonfmt/go.sum
new file mode 100644
index 0000000..7e5f1f7
--- /dev/null
+++ b/jsonfmt/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/memfile/LICENSE.md b/memfile/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/memfile/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/memfile/go.mod b/memfile/go.mod
new file mode 100644
index 0000000..64d66c3
--- /dev/null
+++ b/memfile/go.mod
@@ -0,0 +1,3 @@
+module github.com/dsnet/golib/memfile
+
+go 1.12
diff --git a/unitconv/LICENSE.md b/unitconv/LICENSE.md
new file mode 100644
index 0000000..26c8ba0
--- /dev/null
+++ b/unitconv/LICENSE.md
@@ -0,0 +1,24 @@
+Copyright © 2014, Joe Tsai and The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+* Neither the copyright holder nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior
+written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/unitconv/go.mod b/unitconv/go.mod
new file mode 100644
index 0000000..883d47b
--- /dev/null
+++ b/unitconv/go.mod
@@ -0,0 +1,5 @@
+module github.com/dsnet/golib/unitconv
+
+go 1.12
+
+require github.com/google/go-cmp v0.3.0
diff --git a/unitconv/go.sum b/unitconv/go.sum
new file mode 100644
index 0000000..7e5f1f7
--- /dev/null
+++ b/unitconv/go.sum
@@ -0,0 +1,2 @@
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/unitconv/unitconv.go b/unitconv/unitconv.go
index 57b8914..2efa302 100644
--- a/unitconv/unitconv.go
+++ b/unitconv/unitconv.go
@@ -93,25 +93,25 @@ const (
 
 // Prefix factors according to SI standards.
 const (
-	Yocto = 1E-24
-	Zepto = 1E-21
-	Atto  = 1E-18
-	Femto = 1E-15
-	Pico  = 1E-12
-	Nano  = 1E-9
-	Micro = 1E-6
-	Milli = 1E-3
-
-	Unit = 1E0 // Not a standard SI prefix.
-
-	Kilo  = 1E+3
-	Mega  = 1E+6
-	Giga  = 1E+9
-	Tera  = 1E+12
-	Peta  = 1E+15
-	Exa   = 1E+18
-	Zetta = 1E+21
-	Yotta = 1E+24
+	Yocto = 1e-24
+	Zepto = 1e-21
+	Atto  = 1e-18
+	Femto = 1e-15
+	Pico  = 1e-12
+	Nano  = 1e-9
+	Micro = 1e-6
+	Milli = 1e-3
+
+	Unit = 1e0 // Not a standard SI prefix.
+
+	Kilo  = 1e+3
+	Mega  = 1e+6
+	Giga  = 1e+9
+	Tera  = 1e+12
+	Peta  = 1e+15
+	Exa   = 1e+18
+	Zetta = 1e+21
+	Yotta = 1e+24
 )
 
 const (
diff --git a/unitconv/unitconv_test.go b/unitconv/unitconv_test.go
index 9944768..71b5530 100644
--- a/unitconv/unitconv_test.go
+++ b/unitconv/unitconv_test.go
@@ -273,8 +273,8 @@ func TestParsePrefix(t *testing.T) {
 		{"123K", AutoParse, 123 * Kilo, nil},
 		{"3Mi", AutoParse, 3 * Mebi, nil},
 		{"3M", AutoParse, 3 * Mega, nil},
-		{"3E-3", AutoParse, 3E-3, nil},
-		{"2E2", AutoParse, 2E2, nil},
+		{"3E-3", AutoParse, 3e-3, nil},
+		{"2E2", AutoParse, 2e2, nil},
 	}
 
 	for _, tt := range tests {

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/go.mod
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/decode.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/float.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/float_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/format.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/format_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/string.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/string_test.go
-rw-r--r--  root/root   /usr/share/gocode/src/github.com/dsnet/golib/jsoncs/valid.go

No differences were encountered in the control files

More details

Full run details