Skip to content

Commit

Permalink
feat: add compatible API (#233)
Browse files Browse the repository at this point in the history
* feat: add compatible API

* test: add api compatibility test

* fmt

* test: adjust CI

* fix: no trailing '\n' for std `Marshal()`

* doc: update README.md

* test: add windows CI

* doc: update README.md

* doc: update README.md

* fmt: add comments and refator
  • Loading branch information
AsterDY authored May 25, 2022
1 parent 8917a0a commit ced2830
Show file tree
Hide file tree
Showing 17 changed files with 3,702 additions and 3,064 deletions.
File renamed without changes.
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Push Check Linux
name: Push Check All

on: push

Expand All @@ -7,7 +7,8 @@ jobs:
strategy:
matrix:
go-version: [1.15.x, 1.16.x, 1.17.x]
runs-on: self-hosted
os: [self-hosted]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

Expand All @@ -24,4 +25,4 @@ jobs:
${{ runner.os }}-go-
- name: Unit Test
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 ./...
run: go test -v -gcflags=-d=checkptr=0 ./...
27 changes: 27 additions & 0 deletions .github/workflows/push-check-arm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Push Check ARM

on: push

jobs:
build:
strategy:
matrix:
go-version: [1.15.x, 1.18.x]
runs-on: [self-hosted, arm]
steps:
- uses: actions/checkout@v2

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

- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Compatibility Test
run: go test -v -gcflags=-d=checkptr=0 .
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ jobs:
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race ./...

- name: Generic Test
run: go test -v -gcflags=-d=checkptr=0 -race ./generic_test
run: GOMAXPROCS=4 go test -v -gcflags=-d=checkptr=0 -race ./generic_test
43 changes: 22 additions & 21 deletions .github/workflows/push-check-windows.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
# name: Push Check Go Windows
name: Push Check Windows

# on: push
on: push

# jobs:
# build:
# strategy:
# matrix:
# go-version: [1.15.x, 1.16.x, 1.17.x, 1.18.x]
# os: [windows-latest]
# runs-on: ${{ matrix.os }}
# steps:
# - uses: actions/checkout@v2
jobs:
build:
strategy:
matrix:
go-version: [1.15.x, 1.18.x]
runs-on: windows-latest
steps:
- uses: actions/checkout@v2

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

# - name: Unit Test
# run: go test -v -gcflags -d=checkptr=0 -covermode atomic -coverprofile coverage.out ./...
# env:
# GOMAXPROCS: 4
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# - name: Generic Test
# run: go test -v -gcflags -d=checkptr=0 -covermode atomic ./generic_test
- name: Compatibility Test
run: go test -v -gcflags=-d=checkptr=0 .
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A blazingly fast JSON serializing & deserializing library, accelerated by JI

## Requirement
- Go 1.15/1.16/1.17/1.18
- Linux/darwin OS
- Linux/MacOS/Windows
- Amd64 CPU with AVX instruction set

## Features
Expand Down Expand Up @@ -260,6 +260,12 @@ println(string(buf) == string(exp)) // true
- iteration: `Values()`, `Properties()`, `ForEach()`, `SortKeys()`
- modification: `Set()`, `SetByIndex()`, `Add()`

## Compatibility
Sonic **DOSE NOT** ensure to support all environments, due to the difficulty of developing high-performance codes. For developers who use sonic to build their applications in different environments (ex: developing on M1 Mac but running on linux server), or those who want to handle JSON strictly consistent with `encoding/json`, we provide some compatible APIs as `sonic.API`
- `ConfigDefault`: the sonic's default config (`EscapeHTML=false`,`SortKeys=false`...) to run on sonic-supporting environment. It will fall back to `encoding/json` with corresponding config , and some options like `SortKeys=false` will be invalid.
- `ConfigStd`: the std-compatible config (`EscapeHTML=true`,`SortKeys=true`...) to run on sonic-supporting environment. It whill fall back to `encoding/json`.
- `ConfigFastest`: the fastest config (`NoQuoteTextMarshaler=true`) to run on sonic-supporting environment. It will fall back to `encoding/json` with corresponding config , and some options will be invalid.

## Tips

### Pretouch
Expand Down Expand Up @@ -316,4 +322,7 @@ Why? Because `ast.Node` stores its children using `array`:
- **Hashing** (`map[x]`) is not as efficient as **Indexing** (`array[x]`), which `ast.Node` can conduct on **both array and object**;
- Using `Interface()`/`Map()` means Sonic must parse all the underlying values, while `ast.Node` can parse them **on demand**.

**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, your can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`
**CAUTION:** `ast.Node` **DOESN'T** ensure concurrent security directly, due to its **lazy-load** design. However, your can call `Node.Load()`/`Node.LoadAll()` to achieve that, which may bring performance reduction while it still works faster than converting to `map` or `interface{}`

## Community
Sonic is a subproject of [CloudWeGo](https://www.cloudwego.io/). We are committed to building a cloud native ecosystem.
127 changes: 127 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2021 ByteDance Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sonic

import (
`io`
)

// Config is a combination of sonic/encoder.Options and sonic/decoder.Options
type Config struct {
EscapeHTML bool
SortMapKeys bool
CompactMarshaler bool
NoQuoteTextMarshaler bool
UseInt64 bool
UseNumber bool
UseUnicodeErrors bool
DisallowUnknownFields bool
CopyString bool
}

var (
// ConfigDefault is the default config of APIs, aiming at efficiency and safty.
ConfigDefault = Config{}.Froze()

// ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json.
ConfigStd = Config{
EscapeHTML : true,
SortMapKeys: true,
CompactMarshaler: true,
CopyString : true,
}.Froze()

// ConfigFastest is the fastest config of APIs, aiming at speed.
ConfigFastest = Config{
NoQuoteTextMarshaler: true,
}.Froze()
)


// API a binding of specific config.
// This interface is inspired by github.com/json-iterator/go,
// and has same behaviors under equavilent config.
type API interface {
// MarshalToString returns the JSON encoding string of v
MarshalToString(v interface{}) (string, error)
// Marshal returns the JSON encoding bytes of v.
Marshal(v interface{}) ([]byte, error)
// MarshalIndent returns the JSON encoding bytes with indent and prefix.
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
// UnmarshalFromString parses the JSON-encoded bytes and stores the result in the value pointed to by v.
UnmarshalFromString(str string, v interface{}) error
// Unmarshal parses the JSON-encoded string and stores the result in the value pointed to by v.
Unmarshal(data []byte, v interface{}) error
// NewEncoder create a Encoder holding writer
NewEncoder(writer io.Writer) Encoder
// NewDecoder create a Decoder holding reader
NewDecoder(reader io.Reader) Decoder
// Valid validates the JSON-encoded bytes and reportes if it is valid
Valid(data []byte) bool
}

// Encoder encodes JSON into io.Writer
type Encoder interface {
// Encode writes the JSON encoding of v to the stream, followed by a newline character.
Encode(val interface{}) error
// SetEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior NOT ESCAPE
SetEscapeHTML(on bool)
// SetIndent instructs the encoder to format each subsequent encoded value
// as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation
SetIndent(prefix, indent string)
}

// Decoder decodes JSON from io.Read
type Decoder interface {
// Decode reads the next JSON-encoded value from its input and stores it in the value pointed to by v.
Decode(val interface{}) error
// Buffered returns a reader of the data remaining in the Decoder's buffer.
// The reader is valid until the next call to Decode.
Buffered() io.Reader
// DisallowUnknownFields causes the Decoder to return an error when the destination is a struct
// and the input contains object keys which do not match any non-ignored, exported fields in the destination.
DisallowUnknownFields()
// More reports whether there is another element in the current array or object being parsed.
More() bool
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a Number instead of as a float64.
UseNumber()
}

// Marshal returns the JSON encoding bytes of v.
func Marshal(val interface{}) ([]byte, error) {
return ConfigDefault.Marshal(val)
}

// MarshalString returns the JSON encoding string of v.
func MarshalString(val interface{}) (string, error) {
return ConfigDefault.MarshalToString(val)
}

// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
// NOTICE: This API copies given buffer by default,
// if you want to pass JSON more efficiently, use UnmarshalString instead.
func Unmarshal(buf []byte, val interface{}) error {
return ConfigDefault.Unmarshal(buf, val)
}

// UnmarshalString is like Unmarshal, except buf is a string.
func UnmarshalString(buf string, val interface{}) error {
return ConfigDefault.UnmarshalFromString(buf, val)
}
Loading

0 comments on commit ced2830

Please sign in to comment.