Skip to content

Commit

Permalink
Merge pull request #11 from nspcc-dev/fix-dotnet-compat
Browse files Browse the repository at this point in the history
Fix dotnet compatibility
  • Loading branch information
AnnaShaleva authored Feb 29, 2024
2 parents 296698a + a2ece04 commit fdcc534
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 163 deletions.
34 changes: 8 additions & 26 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,64 +15,46 @@ env:
jobs:
test_cover:
name: Coverage
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04

env:
CGO_ENABLED: 0
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.21

- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}

- name: Update Go modules
run: go mod download -json

- name: Write coverage profile
run: go test -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/...

- name: Upload coverage results to Codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: false
path_to_write_report: ./coverage.txt
verbose: true

tests:
name: Go
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04
strategy:
matrix:
go_versions: [ '1.19', '1.20' ]
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: '${{ matrix.go_versions }}'

- name: Restore Go modules from cache
uses: actions/cache@v2
with:
path: /home/runner/go/pkg/mod
key: deps-${{ hashFiles('go.sum') }}

- name: Update Go modules
run: go mod download -json

- name: Run tests
run: go test -v -race ./...
run: go test -v -race ./...
41 changes: 12 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
nspcc-dev fork of go-ordered-json
===============
.NET-compatible JSON library
============================

There are some legacy/stupid applications[1] that you need to interoperate with,
and they for whatever reason require that the JSON you're using is ordered in
a particular way (contrary to the JSON specifications).
It's made for 100% compatibility with the JSON variation used by
[Neo blockchain](https://github.com/neo-project/). There are three problems
there:
* it's ordered (that's why it's a fork of [go-ordered-json](https://github.com/virtuald/go-ordered-json))
* it has different conventions regarding control and "special" symbols
* it has different conventions wrt incorrect UTF-8

Unfortunately, the golang authors are not willing to support such a broken use
case, so on [their advice](https://groups.google.com/forum/#!topic/golang-dev/zBQwhm3VfvU)
this is a fork of the golang encoding/json package, with the ordered JSON
support originating with a patch from
[Peter Waldschmidt](https://go-review.googlesource.com/c/7930/).
The primary user of this library is [NeoGo](https://github.com/nspcc-dev/neo-go/),
it has to be 100% compatible with C# implementation to correctly process
transactions, that's why we're maintaining this library and solving any
inconsistencies with .NET libraries if found.

**If you can, you should avoid using this package**. However, if you can't
avoid it, then you are welcome to. Provided under the MIT license, just like
golang.

Known broken applications
-------------------------

* [1][Windows Communication Foundation Json __type ordering](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/stand-alone-json-serialization#type-hint-position-in-json-objects)
* [2][NEO node](https://github.com/neo-project/neo/tree/master/src/neo/IO/Json)

Fork motivations
-------------------------

NEO project has its own implementation of JSON serializer which is more a JSON
dialect rather than standard-compatible implementation. Until JSON serialisation
format affects contract states we need to be byte-to-byte compatible with the
reference JSON serializer. This fork contains the following compatibility quirks:

* JSON serializer is ordered (see https://github.com/nspcc-dev/neo-go/pull/2026) (implemented in the original [virtuald/go-ordered-json](https://github.com/virtuald/go-ordered-json) repository)
* JSON serializer escapes non-ascii characters while marshalling (see https://github.com/nspcc-dev/neo-go/pull/2174)

More compatibility quirks may be added in the future.
7 changes: 3 additions & 4 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,13 @@ import (
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// ``not present,'' unmarshaling a JSON null into any other Go type has no effect
// not present, unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
//
func Unmarshal(data []byte, v interface{}) error {
// Check for well-formedness.
// Avoids filling out half a data structure
Expand Down Expand Up @@ -274,8 +273,8 @@ type decodeState struct {
Struct string
Field string
}
savedError error
useNumber bool
savedError error
useNumber bool
useOrderedObject bool
}

Expand Down
22 changes: 11 additions & 11 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,12 +372,12 @@ func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error {
}

type unmarshalTest struct {
in string
ptr interface{}
out interface{}
err error
useNumber bool
golden bool
in string
ptr interface{}
out interface{}
err error
useNumber bool
golden bool
useOrderedObject bool
}

Expand Down Expand Up @@ -858,12 +858,12 @@ func TestMarshal(t *testing.T) {
var badUTF8 = []struct {
in, out string
}{
{"hello\xffworld", `"hello\ufffdworld"`},
{"hello\xffworld", `"hello\u00FFworld"`},
{"", `""`},
{"\xff", `"\ufffd"`},
{"\xff\xff", `"\ufffd\ufffd"`},
{"a\xffb", `"a\ufffdb"`},
{"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"\u65E5\u672C\ufffd\ufffd\ufffd"`},
{"\xff", `"\u00FF"`},
{"\xff\xff", `"\u00FF\u00FF"`},
{"a\xffb", `"a\u00FFb"`},
{"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"\u65E5\u672C\u00FF\u00AA\u009E"`},
}

func TestMarshalBadUTF8(t *testing.T) {
Expand Down
51 changes: 33 additions & 18 deletions encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,31 @@ import (
//
// Examples of struct field tags and their meanings:
//
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
//
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
//
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
//
// // Field is ignored by this package.
// Field int `json:"-"`
// // Field is ignored by this package.
// Field int `json:"-"`
//
// // Field appears in JSON as key "-".
// Field int `json:"-,"`
// // Field appears in JSON as key "-".
// Field int `json:"-,"`
//
// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
// Int64String int64 `json:",string"`
//
// The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, and ASCII punctuation except quotation
Expand Down Expand Up @@ -161,7 +161,6 @@ import (
// JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in
// an infinite recursion.
//
func Marshal(v interface{}) ([]byte, error) {
e := &encodeState{}
err := e.marshal(v, encOpts{escapeHTML: true})
Expand Down Expand Up @@ -917,9 +916,15 @@ func (e *encodeState) string(s string, escapeHTML bool) int {
case '\\':
e.WriteByte('\\')
e.WriteByte(b)
case 0x08:
e.WriteByte('\\')
e.WriteByte('b')
case '\n':
e.WriteByte('\\')
e.WriteByte('n')
case 0x0c:
e.WriteByte('\\')
e.WriteByte('f')
case '\r':
e.WriteByte('\\')
e.WriteByte('r')
Expand All @@ -945,7 +950,9 @@ func (e *encodeState) string(s string, escapeHTML bool) int {
if start < i {
e.WriteString(s[start:i])
}
e.WriteString(`\ufffd`)
e.WriteString(`\u00`)
e.WriteByte(hex[s[i]>>4])
e.WriteByte(hex[s[i]&0xF])
i += size
start = i
continue
Expand Down Expand Up @@ -1004,9 +1011,15 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int {
case '\\':
e.WriteByte('\\')
e.WriteByte(b)
case 0x08:
e.WriteByte('\\')
e.WriteByte('b')
case '\n':
e.WriteByte('\\')
e.WriteByte('n')
case 0x0c:
e.WriteByte('\\')
e.WriteByte('f')
case '\r':
e.WriteByte('\\')
e.WriteByte('r')
Expand All @@ -1032,7 +1045,9 @@ func (e *encodeState) stringBytes(s []byte, escapeHTML bool) int {
if start < i {
e.Write(s[start:i])
}
e.WriteString(`\ufffd`)
e.WriteString(`\u00`)
e.WriteByte(hex[s[i]>>4])
e.WriteByte(hex[s[i]&0xF])
i += size
start = i
continue
Expand Down
45 changes: 10 additions & 35 deletions encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ func (CText) MarshalText() ([]byte, error) {
return []byte(`"<&>"`), nil
}


func TestMarshaler_NeoGo_PR2174(t *testing.T) {
source := "IOU(欠条币):一种支持负数的NEP-17(非严格意义上的)资产,合约无存储区,账户由区块链浏览器统计"
b, err := Marshal(source)
Expand Down Expand Up @@ -578,40 +577,16 @@ var encodeStringTests = []struct {
in string
out string
}{
{"\x00", `"\u0000"`},
{"\x01", `"\u0001"`},
{"\x02", `"\u0002"`},
{"\x03", `"\u0003"`},
{"\x04", `"\u0004"`},
{"\x05", `"\u0005"`},
{"\x06", `"\u0006"`},
{"\x07", `"\u0007"`},
{"\x08", `"\u0008"`},
{"\x09", `"\t"`},
{"\x0a", `"\n"`},
{"\x0b", `"\u000B"`},
{"\x0c", `"\u000C"`},
{"\x0d", `"\r"`},
{"\x0e", `"\u000E"`},
{"\x0f", `"\u000F"`},
{"\x10", `"\u0010"`},
{"\x11", `"\u0011"`},
{"\x12", `"\u0012"`},
{"\x13", `"\u0013"`},
{"\x14", `"\u0014"`},
{"\x15", `"\u0015"`},
{"\x16", `"\u0016"`},
{"\x17", `"\u0017"`},
{"\x18", `"\u0018"`},
{"\x19", `"\u0019"`},
{"\x1a", `"\u001A"`},
{"\x1b", `"\u001B"`},
{"\x1c", `"\u001C"`},
{"\x1d", `"\u001D"`},
{"\x1e", `"\u001E"`},
{"\x1f", `"\u001F"`},
{"'", `"\u0027"`},
{"\"", `"\u0022"`},
{"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", `"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000B\f\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F"`},
{"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", `" !\u0022#$%\u0026\u0027()*\u002B,-./0123456789:;\u003C=\u003E?"`},
{"\x40\x41\x44\x45\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x54\x55\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", `"@ADEDEFGHIJKLMNOPQTUTUVWXYZ[\\]^_"`},
{"\x60\x61\x66\x67\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x76\x77\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", `"\u0060afgdefghijklmnopqvwtuvwxyz{|}~\u007F"`},
{"\x80\x81\x88\x89\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x98\x99\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", `"\u0080\u0081\u0088\u0089\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0098\u0099\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F"`},
{"\xa0\xa1\xaa\xab\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xba\xbb\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", `"\u00A0\u00A1\u00AA\u00AB\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00BA\u00BB\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF"`},
{"\xc0\xc1\xcc\xcd\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xdc\xdd\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", `"\u00C0\u00C1\u00CC\u00CD\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00DC\u00DD\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF"`},
{"\xe0\xe1\xee\xef\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xfe\xff\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", `"\u00E0\u00E1\u00EE\u00EF\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00FE\u00FF\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"`},
{"\xff\xd0", `"\u00FF\u00D0"`},
{"测试", `"\u6D4B\u8BD5"`},
}

func TestEncodeString(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ package json_test

import (
"bytes"
"github.com/virtuald/go-ordered-json"
"fmt"
"github.com/virtuald/go-ordered-json"
"io"
"log"
"os"
Expand Down Expand Up @@ -312,4 +312,4 @@ func ExampleOrderedObject() {
// name=André-Marie Ampère born=1777 died=1836
// Encoded:
// {"name":"Hans Christian Ørsted","born":1777,"died":1851,"nationality":"Danish"}
}
}
5 changes: 3 additions & 2 deletions fold.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ const (
// 4) simpleLetterEqualFold, no specials, no non-letters.
//
// The letters S and K are special because they map to 3 runes, not just 2:
// * S maps to s and to U+017F 'ſ' Latin small letter long s
// * k maps to K and to U+212A 'K' Kelvin sign
// - S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A 'K' Kelvin sign
//
// See https://play.golang.org/p/tTxjOc0OGo
//
// The returned function is specialized for matching against s and
Expand Down
Loading

0 comments on commit fdcc534

Please sign in to comment.