mirror of
https://github.com/netbirdio/easyjson.git
synced 2026-05-22 18:44:42 -07:00
Initial commit.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2016 Mail.Ru Group
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,34 @@
|
||||
PKG=github.com/mailru/easyjson
|
||||
GOPATH:=$(PWD)/.root:$(GOPATH)
|
||||
export GOPATH
|
||||
|
||||
all: test
|
||||
|
||||
.root/src/$(PKG):
|
||||
mkdir -p $@
|
||||
ln -st $@ $$PWD/*
|
||||
|
||||
root: .root/src/$(PKG)
|
||||
|
||||
clean:
|
||||
rm -rf .root
|
||||
|
||||
build:
|
||||
go build -i -o .root/bin/easyjson $(PKG)/easyjson
|
||||
|
||||
generate: root build
|
||||
.root/bin/easyjson -stubs .root/src/$(PKG)/tests/{snake,omitempty,data}.go
|
||||
.root/bin/easyjson -all .root/src/$(PKG)/tests/data.go
|
||||
.root/bin/easyjson -snake_case .root/src/$(PKG)/tests/snake.go
|
||||
.root/bin/easyjson -omit_empty .root/src/$(PKG)/tests/omitempty.go
|
||||
.root/bin/easyjson -build_tags=use_easyjson .root/src/$(PKG)/benchmark/data.go
|
||||
|
||||
test: generate root
|
||||
go test $(PKG)/{tests,jlexer,gen}
|
||||
@go test -benchmem -bench . $(PKG)/benchmark
|
||||
@go test -benchmem -tags use_ffjson -bench . $(PKG)/benchmark
|
||||
@go test -benchmem -tags use_easyjson -bench . $(PKG)/benchmark
|
||||
benchmark/ujson.sh
|
||||
|
||||
|
||||
.PHONY: root clean generate test build
|
||||
@@ -0,0 +1,160 @@
|
||||
# easyjson
|
||||
|
||||
easyjson allows to (un-)marshal JSON golang structs without the use of reflection by generating marshaller code.
|
||||
|
||||
One of the aims of the library is to keep generated code simple enough so that it can be easily optimized or fixed. Another goal is to provide users with ability to customize the generated code not available in 'encoding/json', such as generating snake_case names or enabling 'omitempty' behavior by default.
|
||||
|
||||
## usage
|
||||
```
|
||||
go get github.com/mailru/easyjson/...
|
||||
easyjson -all <file>.go
|
||||
```
|
||||
|
||||
This will generate `<file>_easyjson.go` with marshaller/unmarshaller methods for structs. `GOPATH` variable needs to be set up correctly, since the generation invokes a `go run` on a temporary file (this is a really convenient approach to code generation borrowed from https://github.com/pquerna/ffjson).
|
||||
|
||||
## options
|
||||
```
|
||||
Usage of easyjson:
|
||||
-all
|
||||
generate un-/marshallers for all structs in a file
|
||||
-build_tags string
|
||||
build tags to add to generated file
|
||||
-leave_temps
|
||||
do not delete temporary files
|
||||
-omit_empty
|
||||
omit empty fields by default
|
||||
-snake_case
|
||||
use snake_case names instead of CamelCase by default
|
||||
-stubs
|
||||
only generate stubs for marshallers/unmarshallers methods
|
||||
```
|
||||
|
||||
Using `-all` will generate (un-)marshallers for all structs in the file. By default, structs need to have a line beginning with `easyjson:json` in their docstring, e.g.:
|
||||
```
|
||||
//easyjson:json
|
||||
struct A{}
|
||||
```
|
||||
|
||||
`-snake_case` tells easyjson to generate snake\_case field names by default (unless explicitly overriden by a field tag). The CamelCase to snake\_case conversion algorithm should work in most cases (e.g. HTTPVersion will be converted to http_version). There can be names like JSONHTTPRPC where the conversion will return an unexpected result (jsonhttprpc without underscores), but such names require a dictionary to do the conversion and may be ambiguous.
|
||||
|
||||
`-build_tags` will add corresponding build tag line for the generated file.
|
||||
## marshaller/unmarshaller interfaces
|
||||
|
||||
easyjson generates MarshalJSON/UnmarshalJSON methods that are compatible with interfaces from 'encoding/json'. They are usable with 'json.Marshal' and 'json.Unmarshal' functions, however actually using those will result in significantly worse performance compared to custom interfaces.
|
||||
|
||||
`MarshalEasyJSON` / `UnmarshalEasyJSON` methods are generated for faster parsing using custom Lexer/Writer structs (`jlexer.Lexer` and `jwriter.Writer`). The method signature is defined in `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces. These interfaces allow to avoid using any unnecessary reflection or type assertions during parsing. Functions can be used manually or with `easyjson.Marshal<...>` and `easyjson.Unmarshal<...>` helper methods.
|
||||
|
||||
`jwriter.Writer` struct in addition to function for returning the data as a single slice also has methods to return the size and to send the data to an `io.Writer`. This is aimed at a typical HTTP use-case, when you want to know the `Content-Length` before actually starting to send the data.
|
||||
|
||||
There are helpers in the top-level package for marhsaling/unmarshaling the data using custom interfaces to and from writers, including a helper for `http.ResponseWriter`.
|
||||
|
||||
## custom types
|
||||
If `easyjson.Marshaler` / `easyjson.Unmarshaler` interfaces are implemented by a type involved in JSON parsing, the type will be marshaled/unmarshaled using these methods. `easyjson.Optional` interface allows for a custom type to integrate with 'omitempty' logic.
|
||||
|
||||
As an example, easyjson includes an `easyjson.RawMessage` analogous to `json.RawMessage`.
|
||||
|
||||
Also, there are 'optional' wrappers for primitive types in `easyjson/opt` package. These are useful in the case when it is necessary to distinguish between missing and default value for the type. Wrappers allow to avoid pointers and extra heap allocations in such cases.
|
||||
|
||||
## memory pooling
|
||||
|
||||
The library uses a custom buffer which allocates data in increasing chunks (128-32768 bytes). Chunks of 512 bytes and larger are reused with the help of `sync.Pool`. The maximum size of a chunk is bounded to reduce redundancy in memory allocation and to make the chunks more reusable in the case of large buffer sizes.
|
||||
|
||||
The buffer code is in `easyjson/buffer` package the exact values can be tweaked by a `buffer.Init()` call before the first serialization.
|
||||
|
||||
## limitations
|
||||
* The library is at an early stage, there are likely to be some bugs and some features of 'encoding/json' may not be supported. Please report such cases, so that they may be fixed sooner.
|
||||
* Unsafe package is used by the code. While a non-unsafe version of easyjson can be made in the future, using unsafe package simplifies a lot of code by allowing to use 'switch' for filling out structs and working around limitations of standard functions like 'strconv.ParseInt'.
|
||||
* Floats are currently formatted with default precision for 'strconv' package. It is obvious that it is not always the correct way to handle it, but there aren't enough use-cases for floats at hand to do anything better.
|
||||
* During parsing, parts of JSON that are skipped over are not syntactically validated more than required to skip matching parentheses.
|
||||
* No true streaming support for encoding/decoding. For many use-cases and protocols, data length is typically known on input and needs to be known before sending the data.
|
||||
|
||||
## benchmarks
|
||||
Most benchmarks were done using a sample 13kB JSON (9k if serialized back trimming the whitespace) from https://dev.twitter.com/rest/reference/get/search/tweets. The sample is very close to real-world data, quite structured and contains a variety of different types.
|
||||
|
||||
For small request benchmarks, an 80-byte portion of the regular sample was used.
|
||||
|
||||
For large request marshalling benchmarks, a struct containing 50 regular samples was used, making a ~500kB output JSON.
|
||||
|
||||
Benchmarks are available in the repository and are run on 'make'.
|
||||
|
||||
### easyjson vs. encoding/json
|
||||
|
||||
easyjson seems to be 5-6 times faster than the default json serialization for unmarshalling, 3-4 times faster for non-concurrent marshalling. Concurrent marshalling is 6-7x faster if marshalling to a writer.
|
||||
|
||||
### easyjson vs. ffjson
|
||||
|
||||
easyjson uses the same approach for code generation as ffjson, but a significantly different approach to lexing and generated code. This allows easyjson to be 2-3x faster for unmarshalling and 1.5-2x faster for non-concurrent unmarshalling.
|
||||
|
||||
ffjson seems to behave weird if used concurrently: for large request pooling hurts performance instead of boosting it, it also does not quite scale well. These issues are likely to be fixable and until that comparisons might vary from version to version a lot.
|
||||
|
||||
easyjson is similar in performance for small requests and 2-5x times faster for large ones if used with a writer.
|
||||
|
||||
### easyjson vs 'ujson' python module
|
||||
ujson is using C code for parsing, so it is interesting to see how plain golang compares to that. It is imporant to note that the resulting object for python is slower to access, since the library parses JSON object into dictionaries.
|
||||
|
||||
easyjson seems to be slightly faster for unmarshalling (finally!) and 2-3x faster for marshalling.
|
||||
|
||||
### benchmark figures
|
||||
The data was measured on 28 February, 2016 using current ffjson and golang 1.6
|
||||
|
||||
#### Unmarshalling
|
||||
| lib | json size | MB/s | allocs/op | B/op
|
||||
|--------|-----------|------|-----------|-------
|
||||
|standard| regular | 22 | 218 | 10229
|
||||
|standard| small | 9.7 | 14 | 720
|
||||
|--------|-----------|------|-----------|-------
|
||||
|easyjson| regular | 125 | 128 | 9794
|
||||
|easyjson| small | 67 | 3 | 128
|
||||
|--------|-----------|------|-----------|-------
|
||||
|ffjson | regular | 66 | 141 | 9985
|
||||
|ffjson | small | 17.6 | 10 | 488
|
||||
|--------|-----------|------|-----------|-------
|
||||
|ujson | regular | 103 | N/A | N/A
|
||||
|
||||
#### Marshalling, one goroutine.
|
||||
| lib | json size | MB/s | allocs/op | B/op
|
||||
|----------|-----------|------|-----------|-------
|
||||
|standard | regular | 75 | 9 | 23256
|
||||
|standard | small | 32 | 3 | 328
|
||||
|standard | large | 80 | 17 | 1.2M
|
||||
|----------|-----------|------|-----------|-------
|
||||
|easyjson | regular | 213 | 9 | 10260
|
||||
|easyjson* | regular | 263 | 8 | 742
|
||||
|easyjson | small | 125 | 1 | 128
|
||||
|easyjson | large | 212 | 33 | 490k
|
||||
|easyjson* | large | 262 | 25 | 2879
|
||||
|----------|-----------|------|-----------|-------
|
||||
|ffjson | regular | 122 | 153 | 21340
|
||||
|ffjson** | regular | 146 | 152 | 4897
|
||||
|ffjson | small | 36 | 5 | 384
|
||||
|ffjson** | small | 64 | 4 | 128
|
||||
|ffjson | large | 134 | 7317 | 818k
|
||||
|ffjson** | large | 125 | 7320 | 827k
|
||||
|----------|-----------|------|-----------|-------
|
||||
|ujson | regular | 92 | N/A | N/A
|
||||
\* marshalling to a writer
|
||||
\** using `ffjson.Pool()`
|
||||
|
||||
#### Marshalling, concurrent.
|
||||
| lib | json size | MB/s | allocs/op | B/op
|
||||
|----------|-----------|-------|-----------|-------
|
||||
|standard | regular | 252 | 9 | 23257
|
||||
|standard | small | 124 | 3 | 328
|
||||
|standard | large | 289 | 17 | 1.2M
|
||||
|----------|-----------|-------|-----------|-------
|
||||
|easyjson | regular | 792 | 9 | 10597
|
||||
|easyjson* | regular | 1748 | 8 | 779
|
||||
|easyjson | small | 333 | 1 | 128
|
||||
|easyjson | large | 718 | 36 | 548k
|
||||
|easyjson* | large | 2134 | 25 | 4957
|
||||
|----------|-----------|------|-----------|-------
|
||||
|ffjson | regular | 301 | 153 | 21629
|
||||
|ffjson** | regular | 707 | 152 | 5148
|
||||
|ffjson | small | 62 | 5 | 384
|
||||
|ffjson** | small | 282 | 4 | 128
|
||||
|ffjson | large | 438 | 7330 | 1.0M
|
||||
|ffjson** | large | 131 | 7319 | 820k
|
||||
\* marshalling to a writer
|
||||
\** using `ffjson.Pool()`
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Package benchmark provides a simple benchmark for easyjson against default serialization and ffjson.
|
||||
// The data example is taken from https://dev.twitter.com/rest/reference/get/search/tweets
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var largeStructText, _ = ioutil.ReadFile("example.json")
|
||||
var xlStructData XLStruct
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 50; i++ {
|
||||
xlStructData.Data = append(xlStructData.Data, largeStructData)
|
||||
}
|
||||
}
|
||||
|
||||
var smallStructText = []byte(`{"hashtags":[{"indices":[5, 10],"text":"some-text"}],"urls":[],"user_mentions":[]}`)
|
||||
var smallStructData = Entities{
|
||||
Hashtags: []Hashtag{{Indices: []int{5, 10}, Text: "some-text"}},
|
||||
Urls: []*string{},
|
||||
UserMentions: []*string{},
|
||||
}
|
||||
|
||||
type SearchMetadata struct {
|
||||
CompletedIn float64 `json:"completed_in"`
|
||||
Count int `json:"count"`
|
||||
MaxID int `json:"max_id"`
|
||||
MaxIDStr string `json:"max_id_str"`
|
||||
NextResults string `json:"next_results"`
|
||||
Query string `json:"query"`
|
||||
RefreshURL string `json:"refresh_url"`
|
||||
SinceID int `json:"since_id"`
|
||||
SinceIDStr string `json:"since_id_str"`
|
||||
}
|
||||
|
||||
type Hashtag struct {
|
||||
Indices []int `json:"indices"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type Entities struct {
|
||||
Hashtags []Hashtag `json:"hashtags"`
|
||||
Urls []*string `json:"urls"`
|
||||
UserMentions []*string `json:"user_mentions"`
|
||||
}
|
||||
|
||||
type UserEntityDescription struct {
|
||||
Urls []*string `json:"urls"`
|
||||
}
|
||||
|
||||
type URL struct {
|
||||
ExpandedURL *string `json:"expanded_url"`
|
||||
Indices []int `json:"indices"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type UserEntityURL struct {
|
||||
Urls []URL `json:"urls"`
|
||||
}
|
||||
|
||||
type UserEntities struct {
|
||||
Description UserEntityDescription `json:"description"`
|
||||
URL UserEntityURL `json:"url"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ContributorsEnabled bool `json:"contributors_enabled"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DefaultProfile bool `json:"default_profile"`
|
||||
DefaultProfileImage bool `json:"default_profile_image"`
|
||||
Description string `json:"description"`
|
||||
Entities UserEntities `json:"entities"`
|
||||
FavouritesCount int `json:"favourites_count"`
|
||||
FollowRequestSent *string `json:"follow_request_sent"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
Following *string `json:"following"`
|
||||
FriendsCount int `json:"friends_count"`
|
||||
GeoEnabled bool `json:"geo_enabled"`
|
||||
ID int `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
IsTranslator bool `json:"is_translator"`
|
||||
Lang string `json:"lang"`
|
||||
ListedCount int `json:"listed_count"`
|
||||
Location string `json:"location"`
|
||||
Name string `json:"name"`
|
||||
Notifications *string `json:"notifications"`
|
||||
ProfileBackgroundColor string `json:"profile_background_color"`
|
||||
ProfileBackgroundImageURL string `json:"profile_background_image_url"`
|
||||
ProfileBackgroundImageURLHTTPS string `json:"profile_background_image_url_https"`
|
||||
ProfileBackgroundTile bool `json:"profile_background_tile"`
|
||||
ProfileImageURL string `json:"profile_image_url"`
|
||||
ProfileImageURLHTTPS string `json:"profile_image_url_https"`
|
||||
ProfileLinkColor string `json:"profile_link_color"`
|
||||
ProfileSidebarBorderColor string `json:"profile_sidebar_border_color"`
|
||||
ProfileSidebarFillColor string `json:"profile_sidebar_fill_color"`
|
||||
ProfileTextColor string `json:"profile_text_color"`
|
||||
ProfileUseBackgroundImage bool `json:"profile_use_background_image"`
|
||||
Protected bool `json:"protected"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
ShowAllInlineMedia bool `json:"show_all_inline_media"`
|
||||
StatusesCount int `json:"statuses_count"`
|
||||
TimeZone string `json:"time_zone"`
|
||||
URL *string `json:"url"`
|
||||
UtcOffset int `json:"utc_offset"`
|
||||
Verified bool `json:"verified"`
|
||||
}
|
||||
|
||||
type StatusMetadata struct {
|
||||
IsoLanguageCode string `json:"iso_language_code"`
|
||||
ResultType string `json:"result_type"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Contributors *string `json:"contributors"`
|
||||
Coordinates *string `json:"coordinates"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Entities Entities `json:"entities"`
|
||||
Favorited bool `json:"favorited"`
|
||||
Geo *string `json:"geo"`
|
||||
ID int64 `json:"id"`
|
||||
IDStr string `json:"id_str"`
|
||||
InReplyToScreenName *string `json:"in_reply_to_screen_name"`
|
||||
InReplyToStatusID *string `json:"in_reply_to_status_id"`
|
||||
InReplyToStatusIDStr *string `json:"in_reply_to_status_id_str"`
|
||||
InReplyToUserID *string `json:"in_reply_to_user_id"`
|
||||
InReplyToUserIDStr *string `json:"in_reply_to_user_id_str"`
|
||||
Metadata StatusMetadata `json:"metadata"`
|
||||
Place *string `json:"place"`
|
||||
RetweetCount int `json:"retweet_count"`
|
||||
Retweeted bool `json:"retweeted"`
|
||||
Source string `json:"source"`
|
||||
Text string `json:"text"`
|
||||
Truncated bool `json:"truncated"`
|
||||
User User `json:"user"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type LargeStruct struct {
|
||||
SearchMetadata SearchMetadata `json:"search_metadata"`
|
||||
Statuses []Status `json:"statuses"`
|
||||
}
|
||||
|
||||
//easyjson:json
|
||||
type XLStruct struct {
|
||||
Data []LargeStruct
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,350 @@
|
||||
package benchmark
|
||||
|
||||
var largeStructData = LargeStruct{
|
||||
SearchMetadata: SearchMetadata{
|
||||
CompletedIn: 0.035,
|
||||
Count: 4,
|
||||
MaxID: 250126199840518145,
|
||||
MaxIDStr: "250126199840518145",
|
||||
NextResults: "?max_id=249279667666817023&q=%23freebandnames&count=4&include_entities=1&result_type=mixed",
|
||||
Query: "%23freebandnames",
|
||||
RefreshURL: "?since_id=250126199840518145&q=%23freebandnames&result_type=mixed&include_entities=1",
|
||||
SinceID: 24012619984051000,
|
||||
SinceIDStr: "24012619984051000",
|
||||
},
|
||||
Statuses: []Status{
|
||||
{
|
||||
Contributors: nil,
|
||||
Coordinates: nil,
|
||||
CreatedAt: "Mon Sep 24 03:35:21 +0000 2012",
|
||||
Entities: Entities{
|
||||
Hashtags: []Hashtag{{
|
||||
Indices: []int{20, 34},
|
||||
Text: "freebandnames"},
|
||||
},
|
||||
Urls: []*string{},
|
||||
UserMentions: []*string{},
|
||||
},
|
||||
Favorited: false,
|
||||
Geo: nil,
|
||||
ID: 250075927172759552,
|
||||
IDStr: "250075927172759552",
|
||||
InReplyToScreenName: nil,
|
||||
InReplyToStatusID: nil,
|
||||
InReplyToStatusIDStr: nil,
|
||||
InReplyToUserID: nil,
|
||||
InReplyToUserIDStr: nil,
|
||||
Metadata: StatusMetadata{
|
||||
IsoLanguageCode: "en",
|
||||
ResultType: "recent",
|
||||
},
|
||||
Place: nil,
|
||||
RetweetCount: 0,
|
||||
Retweeted: false,
|
||||
Source: "<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>",
|
||||
Text: "Aggressive Ponytail #freebandnames",
|
||||
Truncated: false,
|
||||
User: User{
|
||||
ContributorsEnabled: false,
|
||||
CreatedAt: "Mon Apr 26 06:01:55 +0000 2010",
|
||||
DefaultProfile: true,
|
||||
DefaultProfileImage: false,
|
||||
Description: "Born 330 Live 310",
|
||||
Entities: UserEntities{
|
||||
Description: UserEntityDescription{
|
||||
Urls: []*string{},
|
||||
},
|
||||
URL: UserEntityURL{
|
||||
Urls: []URL{{
|
||||
ExpandedURL: nil,
|
||||
Indices: []int{0, 0},
|
||||
URL: "",
|
||||
}},
|
||||
},
|
||||
},
|
||||
FavouritesCount: 0,
|
||||
FollowRequestSent: nil,
|
||||
FollowersCount: 70,
|
||||
Following: nil,
|
||||
FriendsCount: 110,
|
||||
GeoEnabled: true,
|
||||
ID: 137238150,
|
||||
IDStr: "137238150",
|
||||
IsTranslator: false,
|
||||
Lang: "en",
|
||||
ListedCount: 2,
|
||||
Location: "LA, CA",
|
||||
Name: "Sean Cummings",
|
||||
Notifications: nil,
|
||||
ProfileBackgroundColor: "C0DEED",
|
||||
ProfileBackgroundImageURL: "http://a0.twimg.com/images/themes/theme1/bg.png",
|
||||
ProfileBackgroundImageURLHTTPS: "https://si0.twimg.com/images/themes/theme1/bg.png",
|
||||
ProfileBackgroundTile: false,
|
||||
ProfileImageURL: "http://a0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg",
|
||||
ProfileImageURLHTTPS: "https://si0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg",
|
||||
ProfileLinkColor: "0084B4",
|
||||
ProfileSidebarBorderColor: "C0DEED",
|
||||
ProfileSidebarFillColor: "DDEEF6",
|
||||
ProfileTextColor: "333333",
|
||||
ProfileUseBackgroundImage: true,
|
||||
Protected: false,
|
||||
ScreenName: "sean_cummings",
|
||||
ShowAllInlineMedia: false,
|
||||
StatusesCount: 579,
|
||||
TimeZone: "Pacific Time (US & Canada)",
|
||||
URL: nil,
|
||||
UtcOffset: -28800,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Contributors: nil,
|
||||
Coordinates: nil,
|
||||
CreatedAt: "Fri Sep 21 23:40:54 +0000 2012",
|
||||
Entities: Entities{
|
||||
Hashtags: []Hashtag{{
|
||||
Indices: []int{20, 34},
|
||||
Text: "FreeBandNames",
|
||||
}},
|
||||
Urls: []*string{},
|
||||
UserMentions: []*string{},
|
||||
},
|
||||
Favorited: false,
|
||||
Geo: nil,
|
||||
ID: 249292149810667520,
|
||||
IDStr: "249292149810667520",
|
||||
InReplyToScreenName: nil,
|
||||
InReplyToStatusID: nil,
|
||||
InReplyToStatusIDStr: nil,
|
||||
InReplyToUserID: nil,
|
||||
InReplyToUserIDStr: nil,
|
||||
Metadata: StatusMetadata{
|
||||
IsoLanguageCode: "pl",
|
||||
ResultType: "recent",
|
||||
},
|
||||
Place: nil,
|
||||
RetweetCount: 0,
|
||||
Retweeted: false,
|
||||
Source: "web",
|
||||
Text: "Thee Namaste Nerdz. #FreeBandNames",
|
||||
Truncated: false,
|
||||
User: User{
|
||||
ContributorsEnabled: false,
|
||||
CreatedAt: "Tue Apr 07 19:05:07 +0000 2009",
|
||||
DefaultProfile: false,
|
||||
DefaultProfileImage: false,
|
||||
Description: "You will come to Durham, North Carolina. I will sell you some records then, here in Durham, North Carolina. Fun will happen.",
|
||||
Entities: UserEntities{
|
||||
Description: UserEntityDescription{Urls: []*string{}},
|
||||
URL: UserEntityURL{
|
||||
Urls: []URL{{
|
||||
ExpandedURL: nil,
|
||||
Indices: []int{0, 32},
|
||||
URL: "http://bullcityrecords.com/wnng/"}},
|
||||
},
|
||||
},
|
||||
FavouritesCount: 8,
|
||||
FollowRequestSent: nil,
|
||||
FollowersCount: 2052,
|
||||
Following: nil,
|
||||
FriendsCount: 348,
|
||||
GeoEnabled: false,
|
||||
ID: 29516238,
|
||||
IDStr: "29516238",
|
||||
IsTranslator: false,
|
||||
Lang: "en",
|
||||
ListedCount: 118,
|
||||
Location: "Durham, NC",
|
||||
Name: "Chaz Martenstein",
|
||||
Notifications: nil,
|
||||
ProfileBackgroundColor: "9AE4E8",
|
||||
ProfileBackgroundImageURL: "http://a0.twimg.com/profile_background_images/9423277/background_tile.bmp",
|
||||
ProfileBackgroundImageURLHTTPS: "https://si0.twimg.com/profile_background_images/9423277/background_tile.bmp",
|
||||
ProfileBackgroundTile: true,
|
||||
ProfileImageURL: "http://a0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
|
||||
ProfileImageURLHTTPS: "https://si0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
|
||||
ProfileLinkColor: "0084B4",
|
||||
ProfileSidebarBorderColor: "BDDCAD",
|
||||
ProfileSidebarFillColor: "DDFFCC",
|
||||
ProfileTextColor: "333333",
|
||||
ProfileUseBackgroundImage: true,
|
||||
Protected: false,
|
||||
ScreenName: "bullcityrecords",
|
||||
ShowAllInlineMedia: true,
|
||||
StatusesCount: 7579,
|
||||
TimeZone: "Eastern Time (US & Canada)",
|
||||
URL: nil,
|
||||
UtcOffset: -18000,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
Status{
|
||||
Contributors: nil,
|
||||
Coordinates: nil,
|
||||
CreatedAt: "Fri Sep 21 23:30:20 +0000 2012",
|
||||
Entities: Entities{
|
||||
Hashtags: []Hashtag{{
|
||||
Indices: []int{29, 43},
|
||||
Text: "freebandnames",
|
||||
}},
|
||||
Urls: []*string{},
|
||||
UserMentions: []*string{},
|
||||
},
|
||||
Favorited: false,
|
||||
Geo: nil,
|
||||
ID: 249289491129438208,
|
||||
IDStr: "249289491129438208",
|
||||
InReplyToScreenName: nil,
|
||||
InReplyToStatusID: nil,
|
||||
InReplyToStatusIDStr: nil,
|
||||
InReplyToUserID: nil,
|
||||
InReplyToUserIDStr: nil,
|
||||
Metadata: StatusMetadata{
|
||||
IsoLanguageCode: "en",
|
||||
ResultType: "recent",
|
||||
},
|
||||
Place: nil,
|
||||
RetweetCount: 0,
|
||||
Retweeted: false,
|
||||
Source: "web",
|
||||
Text: "Mexican Heaven, Mexican Hell #freebandnames",
|
||||
Truncated: false,
|
||||
User: User{
|
||||
ContributorsEnabled: false,
|
||||
CreatedAt: "Tue Sep 01 21:21:35 +0000 2009",
|
||||
DefaultProfile: false,
|
||||
DefaultProfileImage: false,
|
||||
Description: "Science Fiction Writer, sort of. Likes Superheroes, Mole People, Alt. Timelines.",
|
||||
Entities: UserEntities{
|
||||
Description: UserEntityDescription{
|
||||
Urls: nil,
|
||||
},
|
||||
URL: UserEntityURL{
|
||||
Urls: []URL{{
|
||||
ExpandedURL: nil,
|
||||
Indices: []int{0, 0},
|
||||
URL: "",
|
||||
}},
|
||||
},
|
||||
},
|
||||
FavouritesCount: 19,
|
||||
FollowRequestSent: nil,
|
||||
FollowersCount: 63,
|
||||
Following: nil,
|
||||
FriendsCount: 63,
|
||||
GeoEnabled: false,
|
||||
ID: 70789458,
|
||||
IDStr: "70789458",
|
||||
IsTranslator: false,
|
||||
Lang: "en",
|
||||
ListedCount: 1,
|
||||
Location: "Kingston New York",
|
||||
Name: "Thomas John Wakeman",
|
||||
Notifications: nil,
|
||||
ProfileBackgroundColor: "352726",
|
||||
ProfileBackgroundImageURL: "http://a0.twimg.com/images/themes/theme5/bg.gif",
|
||||
ProfileBackgroundImageURLHTTPS: "https://si0.twimg.com/images/themes/theme5/bg.gif",
|
||||
ProfileBackgroundTile: false,
|
||||
ProfileImageURL: "http://a0.twimg.com/profile_images/2219333930/Froggystyle_normal.png",
|
||||
ProfileImageURLHTTPS: "https://si0.twimg.com/profile_images/2219333930/Froggystyle_normal.png",
|
||||
ProfileLinkColor: "D02B55",
|
||||
ProfileSidebarBorderColor: "829D5E",
|
||||
ProfileSidebarFillColor: "99CC33",
|
||||
ProfileTextColor: "3E4415",
|
||||
ProfileUseBackgroundImage: true,
|
||||
Protected: false,
|
||||
ScreenName: "MonkiesFist",
|
||||
ShowAllInlineMedia: false,
|
||||
StatusesCount: 1048,
|
||||
TimeZone: "Eastern Time (US & Canada)",
|
||||
URL: nil,
|
||||
UtcOffset: -18000,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
Status{
|
||||
Contributors: nil,
|
||||
Coordinates: nil,
|
||||
CreatedAt: "Fri Sep 21 22:51:18 +0000 2012",
|
||||
Entities: Entities{
|
||||
Hashtags: []Hashtag{{
|
||||
Indices: []int{20, 34},
|
||||
Text: "freebandnames",
|
||||
}},
|
||||
Urls: []*string{},
|
||||
UserMentions: []*string{},
|
||||
},
|
||||
Favorited: false,
|
||||
Geo: nil,
|
||||
ID: 249279667666817024,
|
||||
IDStr: "249279667666817024",
|
||||
InReplyToScreenName: nil,
|
||||
InReplyToStatusID: nil,
|
||||
InReplyToStatusIDStr: nil,
|
||||
InReplyToUserID: nil,
|
||||
InReplyToUserIDStr: nil,
|
||||
Metadata: StatusMetadata{
|
||||
IsoLanguageCode: "en",
|
||||
ResultType: "recent",
|
||||
},
|
||||
Place: nil,
|
||||
RetweetCount: 0,
|
||||
Retweeted: false,
|
||||
Source: "<a href=\"//twitter.com/download/iphone%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for iPhone</a>",
|
||||
Text: "The Foolish Mortals #freebandnames",
|
||||
Truncated: false,
|
||||
User: User{
|
||||
ContributorsEnabled: false,
|
||||
CreatedAt: "Mon May 04 00:05:00 +0000 2009",
|
||||
DefaultProfile: false,
|
||||
DefaultProfileImage: false,
|
||||
Description: "Cartoonist, Illustrator, and T-Shirt connoisseur",
|
||||
Entities: UserEntities{
|
||||
Description: UserEntityDescription{
|
||||
Urls: []*string{},
|
||||
},
|
||||
URL: UserEntityURL{
|
||||
Urls: []URL{{
|
||||
ExpandedURL: nil,
|
||||
Indices: []int{0, 24},
|
||||
URL: "http://www.omnitarian.me",
|
||||
}},
|
||||
},
|
||||
},
|
||||
FavouritesCount: 647,
|
||||
FollowRequestSent: nil,
|
||||
FollowersCount: 608,
|
||||
Following: nil,
|
||||
FriendsCount: 249,
|
||||
GeoEnabled: false,
|
||||
ID: 37539828,
|
||||
IDStr: "37539828",
|
||||
IsTranslator: false,
|
||||
Lang: "en",
|
||||
ListedCount: 52,
|
||||
Location: "Wisconsin, USA",
|
||||
Name: "Marty Elmer",
|
||||
Notifications: nil,
|
||||
ProfileBackgroundColor: "EEE3C4",
|
||||
ProfileBackgroundImageURL: "http://a0.twimg.com/profile_background_images/106455659/rect6056-9.png",
|
||||
ProfileBackgroundImageURLHTTPS: "https://si0.twimg.com/profile_background_images/106455659/rect6056-9.png",
|
||||
ProfileBackgroundTile: true,
|
||||
ProfileImageURL: "http://a0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png",
|
||||
ProfileImageURLHTTPS: "https://si0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png",
|
||||
ProfileLinkColor: "3B2A26",
|
||||
ProfileSidebarBorderColor: "615A44",
|
||||
ProfileSidebarFillColor: "BFAC83",
|
||||
ProfileTextColor: "000000",
|
||||
ProfileUseBackgroundImage: true,
|
||||
Protected: false,
|
||||
ScreenName: "Omnitarian",
|
||||
ShowAllInlineMedia: true,
|
||||
StatusesCount: 3575,
|
||||
TimeZone: "Central Time (US & Canada)",
|
||||
URL: nil,
|
||||
UtcOffset: -21600,
|
||||
Verified: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// +build !use_easyjson,!use_ffjson
|
||||
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkUnmarshalRequestStd(b *testing.B) {
|
||||
b.SetBytes(int64(len(largeStructText)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s LargeStruct
|
||||
err := json.Unmarshal(largeStructText, &s)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestStd(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := json.Marshal(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestStd(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := json.Marshal(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestStdParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := json.Marshal(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestStdParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := json.Marshal(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalSmallRequestStd(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s Entities
|
||||
err := json.Unmarshal(smallStructText, &s)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
b.SetBytes(int64(len(smallStructText)))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestStd(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := json.Marshal(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestStdParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := json.Marshal(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalToWriterStd(b *testing.B) {
|
||||
enc := json.NewEncoder(&DummyWriter{})
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := enc.Encode(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DummyWriter struct{}
|
||||
|
||||
func (w DummyWriter) Write(data []byte) (int, error) { return len(data), nil }
|
||||
|
||||
func TestToSuppressNoTestsWarning(t *testing.T) {}
|
||||
@@ -0,0 +1,184 @@
|
||||
// +build use_easyjson
|
||||
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
func BenchmarkUnmarshalRequestEJ(b *testing.B) {
|
||||
b.SetBytes(int64(len(largeStructText)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s LargeStruct
|
||||
err := s.UnmarshalJSON(largeStructText)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestEJ(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := easyjson.Marshal(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestEJ(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := easyjson.Marshal(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestToWriterEJ(b *testing.B) {
|
||||
var l int64
|
||||
out := &DummyWriter{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := jwriter.Writer{}
|
||||
xlStructData.MarshalEasyJSON(&w)
|
||||
if w.Error != nil {
|
||||
b.Error(w.Error)
|
||||
}
|
||||
|
||||
l = int64(w.Size())
|
||||
w.DumpTo(out)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
|
||||
}
|
||||
func BenchmarkMarshalRequestEJParallel(b *testing.B) {
|
||||
b.SetBytes(int64(len(largeStructText)))
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := largeStructData.MarshalJSON()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMarshalToWriterEJ(b *testing.B) {
|
||||
var l int64
|
||||
out := &DummyWriter{}
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := jwriter.Writer{}
|
||||
largeStructData.MarshalEasyJSON(&w)
|
||||
if w.Error != nil {
|
||||
b.Error(w.Error)
|
||||
}
|
||||
|
||||
l = int64(w.Size())
|
||||
w.DumpTo(out)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
|
||||
}
|
||||
func BenchmarkMarshalRequestToWriterEJParallel(b *testing.B) {
|
||||
out := &DummyWriter{}
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var l int64
|
||||
for pb.Next() {
|
||||
w := jwriter.Writer{}
|
||||
largeStructData.MarshalEasyJSON(&w)
|
||||
if w.Error != nil {
|
||||
b.Error(w.Error)
|
||||
}
|
||||
|
||||
l = int64(w.Size())
|
||||
w.DumpTo(out)
|
||||
}
|
||||
if l > 0 {
|
||||
b.SetBytes(l)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestEJParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := xlStructData.MarshalJSON()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestToWriterEJParallel(b *testing.B) {
|
||||
out := &DummyWriter{}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var l int64
|
||||
for pb.Next() {
|
||||
w := jwriter.Writer{}
|
||||
|
||||
xlStructData.MarshalEasyJSON(&w)
|
||||
if w.Error != nil {
|
||||
b.Error(w.Error)
|
||||
}
|
||||
l = int64(w.Size())
|
||||
w.DumpTo(out)
|
||||
}
|
||||
if l > 0 {
|
||||
b.SetBytes(l)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalSmallRequestEJ(b *testing.B) {
|
||||
b.SetBytes(int64(len(smallStructText)))
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s Entities
|
||||
err := s.UnmarshalJSON(smallStructText)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestEJ(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := smallStructData.MarshalJSON()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestEJParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := smallStructData.MarshalJSON()
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
{
|
||||
"statuses": [
|
||||
{
|
||||
"coordinates": null,
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"created_at": "Mon Sep 24 03:35:21 +0000 2012",
|
||||
"id_str": "250075927172759552",
|
||||
"entities": {
|
||||
"urls": [
|
||||
|
||||
],
|
||||
"hashtags": [
|
||||
{
|
||||
"text": "freebandnames",
|
||||
"indices": [
|
||||
20,
|
||||
34
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_mentions": [
|
||||
|
||||
]
|
||||
},
|
||||
"in_reply_to_user_id_str": null,
|
||||
"contributors": null,
|
||||
"text": "Aggressive Ponytail #freebandnames",
|
||||
"metadata": {
|
||||
"iso_language_code": "en",
|
||||
"result_type": "recent"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"in_reply_to_status_id_str": null,
|
||||
"id": 250075927172759552,
|
||||
"geo": null,
|
||||
"retweeted": false,
|
||||
"in_reply_to_user_id": null,
|
||||
"place": null,
|
||||
"user": {
|
||||
"profile_sidebar_fill_color": "DDEEF6",
|
||||
"profile_sidebar_border_color": "C0DEED",
|
||||
"profile_background_tile": false,
|
||||
"name": "Sean Cummings",
|
||||
"profile_image_url": "http://a0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg",
|
||||
"created_at": "Mon Apr 26 06:01:55 +0000 2010",
|
||||
"location": "LA, CA",
|
||||
"follow_request_sent": null,
|
||||
"profile_link_color": "0084B4",
|
||||
"is_translator": false,
|
||||
"id_str": "137238150",
|
||||
"entities": {
|
||||
"url": {
|
||||
"urls": [
|
||||
{
|
||||
"expanded_url": null,
|
||||
"url": "",
|
||||
"indices": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"urls": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"default_profile": true,
|
||||
"contributors_enabled": false,
|
||||
"favourites_count": 0,
|
||||
"url": null,
|
||||
"profile_image_url_https": "https://si0.twimg.com/profile_images/2359746665/1v6zfgqo8g0d3mk7ii5s_normal.jpeg",
|
||||
"utc_offset": -28800,
|
||||
"id": 137238150,
|
||||
"profile_use_background_image": true,
|
||||
"listed_count": 2,
|
||||
"profile_text_color": "333333",
|
||||
"lang": "en",
|
||||
"followers_count": 70,
|
||||
"protected": false,
|
||||
"notifications": null,
|
||||
"profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme1/bg.png",
|
||||
"profile_background_color": "C0DEED",
|
||||
"verified": false,
|
||||
"geo_enabled": true,
|
||||
"time_zone": "Pacific Time (US & Canada)",
|
||||
"description": "Born 330 Live 310",
|
||||
"default_profile_image": false,
|
||||
"profile_background_image_url": "http://a0.twimg.com/images/themes/theme1/bg.png",
|
||||
"statuses_count": 579,
|
||||
"friends_count": 110,
|
||||
"following": null,
|
||||
"show_all_inline_media": false,
|
||||
"screen_name": "sean_cummings"
|
||||
},
|
||||
"in_reply_to_screen_name": null,
|
||||
"source": "<a href=\"//itunes.apple.com/us/app/twitter/id409789998?mt=12%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for Mac</a>",
|
||||
"in_reply_to_status_id": null
|
||||
},
|
||||
{
|
||||
"coordinates": null,
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"created_at": "Fri Sep 21 23:40:54 +0000 2012",
|
||||
"id_str": "249292149810667520",
|
||||
"entities": {
|
||||
"urls": [
|
||||
|
||||
],
|
||||
"hashtags": [
|
||||
{
|
||||
"text": "FreeBandNames",
|
||||
"indices": [
|
||||
20,
|
||||
34
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_mentions": [
|
||||
|
||||
]
|
||||
},
|
||||
"in_reply_to_user_id_str": null,
|
||||
"contributors": null,
|
||||
"text": "Thee Namaste Nerdz. #FreeBandNames",
|
||||
"metadata": {
|
||||
"iso_language_code": "pl",
|
||||
"result_type": "recent"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"in_reply_to_status_id_str": null,
|
||||
"id": 249292149810667520,
|
||||
"geo": null,
|
||||
"retweeted": false,
|
||||
"in_reply_to_user_id": null,
|
||||
"place": null,
|
||||
"user": {
|
||||
"profile_sidebar_fill_color": "DDFFCC",
|
||||
"profile_sidebar_border_color": "BDDCAD",
|
||||
"profile_background_tile": true,
|
||||
"name": "Chaz Martenstein",
|
||||
"profile_image_url": "http://a0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
|
||||
"created_at": "Tue Apr 07 19:05:07 +0000 2009",
|
||||
"location": "Durham, NC",
|
||||
"follow_request_sent": null,
|
||||
"profile_link_color": "0084B4",
|
||||
"is_translator": false,
|
||||
"id_str": "29516238",
|
||||
"entities": {
|
||||
"url": {
|
||||
"urls": [
|
||||
{
|
||||
"expanded_url": null,
|
||||
"url": "http://bullcityrecords.com/wnng/",
|
||||
"indices": [
|
||||
0,
|
||||
32
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"urls": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"default_profile": false,
|
||||
"contributors_enabled": false,
|
||||
"favourites_count": 8,
|
||||
"url": "http://bullcityrecords.com/wnng/",
|
||||
"profile_image_url_https": "https://si0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
|
||||
"utc_offset": -18000,
|
||||
"id": 29516238,
|
||||
"profile_use_background_image": true,
|
||||
"listed_count": 118,
|
||||
"profile_text_color": "333333",
|
||||
"lang": "en",
|
||||
"followers_count": 2052,
|
||||
"protected": false,
|
||||
"notifications": null,
|
||||
"profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/9423277/background_tile.bmp",
|
||||
"profile_background_color": "9AE4E8",
|
||||
"verified": false,
|
||||
"geo_enabled": false,
|
||||
"time_zone": "Eastern Time (US & Canada)",
|
||||
"description": "You will come to Durham, North Carolina. I will sell you some records then, here in Durham, North Carolina. Fun will happen.",
|
||||
"default_profile_image": false,
|
||||
"profile_background_image_url": "http://a0.twimg.com/profile_background_images/9423277/background_tile.bmp",
|
||||
"statuses_count": 7579,
|
||||
"friends_count": 348,
|
||||
"following": null,
|
||||
"show_all_inline_media": true,
|
||||
"screen_name": "bullcityrecords"
|
||||
},
|
||||
"in_reply_to_screen_name": null,
|
||||
"source": "web",
|
||||
"in_reply_to_status_id": null
|
||||
},
|
||||
{
|
||||
"coordinates": null,
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"created_at": "Fri Sep 21 23:30:20 +0000 2012",
|
||||
"id_str": "249289491129438208",
|
||||
"entities": {
|
||||
"urls": [
|
||||
|
||||
],
|
||||
"hashtags": [
|
||||
{
|
||||
"text": "freebandnames",
|
||||
"indices": [
|
||||
29,
|
||||
43
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_mentions": [
|
||||
|
||||
]
|
||||
},
|
||||
"in_reply_to_user_id_str": null,
|
||||
"contributors": null,
|
||||
"text": "Mexican Heaven, Mexican Hell #freebandnames",
|
||||
"metadata": {
|
||||
"iso_language_code": "en",
|
||||
"result_type": "recent"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"in_reply_to_status_id_str": null,
|
||||
"id": 249289491129438208,
|
||||
"geo": null,
|
||||
"retweeted": false,
|
||||
"in_reply_to_user_id": null,
|
||||
"place": null,
|
||||
"user": {
|
||||
"profile_sidebar_fill_color": "99CC33",
|
||||
"profile_sidebar_border_color": "829D5E",
|
||||
"profile_background_tile": false,
|
||||
"name": "Thomas John Wakeman",
|
||||
"profile_image_url": "http://a0.twimg.com/profile_images/2219333930/Froggystyle_normal.png",
|
||||
"created_at": "Tue Sep 01 21:21:35 +0000 2009",
|
||||
"location": "Kingston New York",
|
||||
"follow_request_sent": null,
|
||||
"profile_link_color": "D02B55",
|
||||
"is_translator": false,
|
||||
"id_str": "70789458",
|
||||
"entities": {
|
||||
"url": {
|
||||
"urls": [
|
||||
{
|
||||
"expanded_url": null,
|
||||
"url": "",
|
||||
"indices": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"urls": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"default_profile": false,
|
||||
"contributors_enabled": false,
|
||||
"favourites_count": 19,
|
||||
"url": null,
|
||||
"profile_image_url_https": "https://si0.twimg.com/profile_images/2219333930/Froggystyle_normal.png",
|
||||
"utc_offset": -18000,
|
||||
"id": 70789458,
|
||||
"profile_use_background_image": true,
|
||||
"listed_count": 1,
|
||||
"profile_text_color": "3E4415",
|
||||
"lang": "en",
|
||||
"followers_count": 63,
|
||||
"protected": false,
|
||||
"notifications": null,
|
||||
"profile_background_image_url_https": "https://si0.twimg.com/images/themes/theme5/bg.gif",
|
||||
"profile_background_color": "352726",
|
||||
"verified": false,
|
||||
"geo_enabled": false,
|
||||
"time_zone": "Eastern Time (US & Canada)",
|
||||
"description": "Science Fiction Writer, sort of. Likes Superheroes, Mole People, Alt. Timelines.",
|
||||
"default_profile_image": false,
|
||||
"profile_background_image_url": "http://a0.twimg.com/images/themes/theme5/bg.gif",
|
||||
"statuses_count": 1048,
|
||||
"friends_count": 63,
|
||||
"following": null,
|
||||
"show_all_inline_media": false,
|
||||
"screen_name": "MonkiesFist"
|
||||
},
|
||||
"in_reply_to_screen_name": null,
|
||||
"source": "web",
|
||||
"in_reply_to_status_id": null
|
||||
},
|
||||
{
|
||||
"coordinates": null,
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"created_at": "Fri Sep 21 22:51:18 +0000 2012",
|
||||
"id_str": "249279667666817024",
|
||||
"entities": {
|
||||
"urls": [
|
||||
|
||||
],
|
||||
"hashtags": [
|
||||
{
|
||||
"text": "freebandnames",
|
||||
"indices": [
|
||||
20,
|
||||
34
|
||||
]
|
||||
}
|
||||
],
|
||||
"user_mentions": [
|
||||
|
||||
]
|
||||
},
|
||||
"in_reply_to_user_id_str": null,
|
||||
"contributors": null,
|
||||
"text": "The Foolish Mortals #freebandnames",
|
||||
"metadata": {
|
||||
"iso_language_code": "en",
|
||||
"result_type": "recent"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"in_reply_to_status_id_str": null,
|
||||
"id": 249279667666817024,
|
||||
"geo": null,
|
||||
"retweeted": false,
|
||||
"in_reply_to_user_id": null,
|
||||
"place": null,
|
||||
"user": {
|
||||
"profile_sidebar_fill_color": "BFAC83",
|
||||
"profile_sidebar_border_color": "615A44",
|
||||
"profile_background_tile": true,
|
||||
"name": "Marty Elmer",
|
||||
"profile_image_url": "http://a0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png",
|
||||
"created_at": "Mon May 04 00:05:00 +0000 2009",
|
||||
"location": "Wisconsin, USA",
|
||||
"follow_request_sent": null,
|
||||
"profile_link_color": "3B2A26",
|
||||
"is_translator": false,
|
||||
"id_str": "37539828",
|
||||
"entities": {
|
||||
"url": {
|
||||
"urls": [
|
||||
{
|
||||
"expanded_url": null,
|
||||
"url": "http://www.omnitarian.me",
|
||||
"indices": [
|
||||
0,
|
||||
24
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"urls": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"default_profile": false,
|
||||
"contributors_enabled": false,
|
||||
"favourites_count": 647,
|
||||
"url": "http://www.omnitarian.me",
|
||||
"profile_image_url_https": "https://si0.twimg.com/profile_images/1629790393/shrinker_2000_trans_normal.png",
|
||||
"utc_offset": -21600,
|
||||
"id": 37539828,
|
||||
"profile_use_background_image": true,
|
||||
"listed_count": 52,
|
||||
"profile_text_color": "000000",
|
||||
"lang": "en",
|
||||
"followers_count": 608,
|
||||
"protected": false,
|
||||
"notifications": null,
|
||||
"profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/106455659/rect6056-9.png",
|
||||
"profile_background_color": "EEE3C4",
|
||||
"verified": false,
|
||||
"geo_enabled": false,
|
||||
"time_zone": "Central Time (US & Canada)",
|
||||
"description": "Cartoonist, Illustrator, and T-Shirt connoisseur",
|
||||
"default_profile_image": false,
|
||||
"profile_background_image_url": "http://a0.twimg.com/profile_background_images/106455659/rect6056-9.png",
|
||||
"statuses_count": 3575,
|
||||
"friends_count": 249,
|
||||
"following": null,
|
||||
"show_all_inline_media": true,
|
||||
"screen_name": "Omnitarian"
|
||||
},
|
||||
"in_reply_to_screen_name": null,
|
||||
"source": "<a href=\"//twitter.com/download/iphone%5C%22\" rel=\"\\\"nofollow\\\"\">Twitter for iPhone</a>",
|
||||
"in_reply_to_status_id": null
|
||||
}
|
||||
],
|
||||
"search_metadata": {
|
||||
"max_id": 250126199840518145,
|
||||
"since_id": 24012619984051000,
|
||||
"refresh_url": "?since_id=250126199840518145&q=%23freebandnames&result_type=mixed&include_entities=1",
|
||||
"next_results": "?max_id=249279667666817023&q=%23freebandnames&count=4&include_entities=1&result_type=mixed",
|
||||
"count": 4,
|
||||
"completed_in": 0.035,
|
||||
"since_id_str": "24012619984051000",
|
||||
"query": "%23freebandnames",
|
||||
"max_id_str": "250126199840518145"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// +build use_ffjson
|
||||
|
||||
package benchmark
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pquerna/ffjson/ffjson"
|
||||
)
|
||||
|
||||
func BenchmarkUnmarshalRequestFF(b *testing.B) {
|
||||
b.SetBytes(int64(len(largeStructText)))
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s LargeStruct
|
||||
err := ffjson.UnmarshalFast(largeStructText, &s)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestFF(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestFF(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestFFPool(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestFF(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestFFPool(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestFFPoolParallel(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
func BenchmarkMarshalRequestFFPoolParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := ffjson.MarshalFast(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalSmallRequestFF(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
var s Entities
|
||||
err := ffjson.UnmarshalFast(smallStructText, &s)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
b.SetBytes(int64(len(smallStructText)))
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestPoolFF(b *testing.B) {
|
||||
var l int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
data, err := ffjson.MarshalFast(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestPoolFFParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := ffjson.MarshalFast(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
ffjson.Pool(data)
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalSmallRequestFFParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := ffjson.MarshalFast(&smallStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalRequestFFParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := ffjson.MarshalFast(&largeStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
|
||||
func BenchmarkMarshalXLRequestFFParallel(b *testing.B) {
|
||||
var l int64
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, err := ffjson.MarshalFast(&xlStructData)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
l = int64(len(data))
|
||||
}
|
||||
})
|
||||
b.SetBytes(l)
|
||||
}
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#/bin/bash
|
||||
|
||||
echo -n "Python ujson module, DECODE: "
|
||||
python -m timeit -s "import ujson; data = open('`dirname $0`/example.json', 'r').read()" 'ujson.loads(data)'
|
||||
|
||||
echo -n "Python ujson module, ENCODE: "
|
||||
python -m timeit -s "import ujson; data = open('`dirname $0`/example.json', 'r').read(); obj = ujson.loads(data)" 'ujson.dumps(obj)'
|
||||
@@ -0,0 +1,128 @@
|
||||
// Package bootstrap implements the bootstrapping logic: generation of a .go file to
|
||||
// launch the actual generator and launching the generator itself.
|
||||
//
|
||||
// The package may be preferred to a command-line utility if generating the serializers
|
||||
// from golang code is required.
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const genPackage = "github.com/mailru/easyjson/gen"
|
||||
const pkgWriter = "github.com/mailru/easyjson/jwriter"
|
||||
const pkgLexer = "github.com/mailru/easyjson/jlexer"
|
||||
|
||||
type Generator struct {
|
||||
PkgPath, PkgName string
|
||||
Types []string
|
||||
|
||||
SnakeCase bool
|
||||
OmitEmpty bool
|
||||
|
||||
OutName string
|
||||
BuildTags string
|
||||
|
||||
StubsOnly bool
|
||||
LeaveTemps bool
|
||||
}
|
||||
|
||||
// writeStub outputs an initial stubs for marshalers/unmarshalers so that the package
|
||||
// using marshalers/unmarshales compiles correctly for boostrapping code.
|
||||
func (g *Generator) writeStub() error {
|
||||
f, err := os.Create(g.OutName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fmt.Fprintln(f, "package ", g.PkgName)
|
||||
fmt.Fprintln(f)
|
||||
|
||||
for _, t := range g.Types {
|
||||
fmt.Fprintln(f, "func (*", t, ") MarshalJSON() ([]byte, error) { return nil, nil }")
|
||||
fmt.Fprintln(f, "func (*", t, ") UnmarshalJSON([]byte) error { return nil }")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeMain creates a .go file that launches the generator if 'go run'.
|
||||
func (g *Generator) writeMain() (path string, err error) {
|
||||
f, err := ioutil.TempFile("", "easyjson")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fmt.Fprintln(f, "package main")
|
||||
fmt.Fprintln(f, "import (")
|
||||
fmt.Fprintln(f, ` "fmt"`)
|
||||
fmt.Fprintln(f, ` "os"`)
|
||||
fmt.Fprintln(f)
|
||||
fmt.Fprintf(f, " %q\n", genPackage)
|
||||
fmt.Fprintln(f)
|
||||
fmt.Fprintf(f, " pkg %q\n", g.PkgPath)
|
||||
fmt.Fprintln(f, ")")
|
||||
fmt.Fprintln(f)
|
||||
fmt.Fprintln(f, "func main() {")
|
||||
fmt.Fprintln(f, " g := gen.NewGenerator()")
|
||||
fmt.Fprintf(f, " g.SetPkg(%q, %q)\n", g.PkgName, g.PkgPath)
|
||||
if g.BuildTags != "" {
|
||||
fmt.Fprintf(f, " g.SetBuildTags(%q)\n", g.BuildTags)
|
||||
}
|
||||
if g.SnakeCase {
|
||||
fmt.Fprintln(f, " g.UseSnakeCase()")
|
||||
}
|
||||
if g.OmitEmpty {
|
||||
fmt.Fprintln(f, " g.OmitEmpty()")
|
||||
}
|
||||
for _, v := range g.Types {
|
||||
fmt.Fprintln(f, " g.Add(pkg."+v+"{})")
|
||||
}
|
||||
|
||||
fmt.Fprintln(f, " if err := g.Run(os.Stdout); err != nil {")
|
||||
fmt.Fprintln(f, " fmt.Fprintln(os.Stderr, err)")
|
||||
fmt.Fprintln(f, " os.Exit(1)")
|
||||
fmt.Fprintln(f, " }")
|
||||
fmt.Fprintln(f, "}")
|
||||
|
||||
p := f.Name()
|
||||
os.Rename(p, p+".go")
|
||||
return p + ".go", f.Close()
|
||||
}
|
||||
|
||||
func (g *Generator) Run() error {
|
||||
if err := g.writeStub(); err != nil {
|
||||
return err
|
||||
}
|
||||
if g.StubsOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
path, err := g.writeMain()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !g.LeaveTemps {
|
||||
defer os.Remove(path)
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile("", "easyjson-out")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !g.LeaveTemps {
|
||||
defer os.Remove(f.Name()) // will not remove after rename
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "run", path)
|
||||
cmd.Stdout = f
|
||||
cmd.Stderr = os.Stderr
|
||||
if err = cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(f.Name(), g.OutName)
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
// Package buffer implements a buffer for serialization, consisting of a chain of []byte-s to
|
||||
// reduce copying and to allow reuse of individual chunks.
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PoolConfig contains configuration for the allocation and reuse strategy.
|
||||
type PoolConfig struct {
|
||||
StartSize int // Minimum chunk size that is allocated.
|
||||
PooledSize int // Minimum chunk size that is reused, reusing chunks too small will result in overhead.
|
||||
MaxSize int // Maximum chunk size that will be allocated.
|
||||
}
|
||||
|
||||
var config = PoolConfig{
|
||||
StartSize: 128,
|
||||
PooledSize: 512,
|
||||
MaxSize: 32768,
|
||||
}
|
||||
|
||||
// Reuse pool: chunk size -> pool.
|
||||
var buffers = map[int]*sync.Pool{}
|
||||
|
||||
func initBuffers() {
|
||||
for l := config.PooledSize; l <= config.MaxSize; l *= 2 {
|
||||
buffers[l] = new(sync.Pool)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
initBuffers()
|
||||
}
|
||||
|
||||
// Init sets up a non-default pooling and allocation strategy. Should be run before serialization is done.
|
||||
func Init(cfg PoolConfig) {
|
||||
config = cfg
|
||||
initBuffers()
|
||||
}
|
||||
|
||||
// putBuf puts a chunk to reuse pool if it can be reused.
|
||||
func putBuf(buf []byte) {
|
||||
size := cap(buf)
|
||||
if size < config.PooledSize {
|
||||
return
|
||||
}
|
||||
if c := buffers[size]; c != nil {
|
||||
c.Put(buf[:0])
|
||||
}
|
||||
}
|
||||
|
||||
// getBuf gets a chunk from reuse pool or creates a new one if reuse failed.
|
||||
func getBuf(size int) []byte {
|
||||
if size < config.PooledSize {
|
||||
return make([]byte, 0, size)
|
||||
}
|
||||
|
||||
if c := buffers[size]; c != nil {
|
||||
v := c.Get()
|
||||
if v != nil {
|
||||
return v.([]byte)
|
||||
}
|
||||
}
|
||||
return make([]byte, 0, size)
|
||||
}
|
||||
|
||||
// Buffer is a buffer optimized for serialization without extra copying.
|
||||
type Buffer struct {
|
||||
|
||||
// Buf is the current chunk that can be used for serialization.
|
||||
Buf []byte
|
||||
|
||||
toPool []byte
|
||||
bufs [][]byte
|
||||
}
|
||||
|
||||
// EnsureSpace makes sure that the current chunk contains at least s free bytes,
|
||||
// possibly creating a new chunk.
|
||||
func (b *Buffer) EnsureSpace(s int) {
|
||||
if cap(b.Buf)-len(b.Buf) >= s {
|
||||
return
|
||||
}
|
||||
l := len(b.Buf)
|
||||
if l > 0 {
|
||||
if cap(b.toPool) != cap(b.Buf) {
|
||||
// Chunk was reallocated, toPool can be pooled.
|
||||
putBuf(b.toPool)
|
||||
}
|
||||
if cap(b.bufs) == 0 {
|
||||
b.bufs = make([][]byte, 0, 8)
|
||||
}
|
||||
b.bufs = append(b.bufs, b.Buf)
|
||||
l = cap(b.toPool) * 2
|
||||
} else {
|
||||
l = config.StartSize
|
||||
}
|
||||
|
||||
if l > config.MaxSize {
|
||||
l = config.MaxSize
|
||||
}
|
||||
b.Buf = getBuf(l)
|
||||
b.toPool = b.Buf
|
||||
}
|
||||
|
||||
// AppendByte appends a single byte to buffer.
|
||||
func (b *Buffer) AppendByte(data byte) {
|
||||
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
|
||||
b.EnsureSpace(1)
|
||||
}
|
||||
b.Buf = append(b.Buf, data)
|
||||
}
|
||||
|
||||
// AppendBytes appends a byte slice to buffer.
|
||||
func (b *Buffer) AppendBytes(data []byte) {
|
||||
for len(data) > 0 {
|
||||
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
|
||||
b.EnsureSpace(1)
|
||||
}
|
||||
|
||||
sz := cap(b.Buf) - len(b.Buf)
|
||||
if sz > len(data) {
|
||||
sz = len(data)
|
||||
}
|
||||
|
||||
b.Buf = append(b.Buf, data[:sz]...)
|
||||
data = data[sz:]
|
||||
}
|
||||
}
|
||||
|
||||
// AppendBytes appends a string to buffer.
|
||||
func (b *Buffer) AppendString(data string) {
|
||||
for len(data) > 0 {
|
||||
if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined.
|
||||
b.EnsureSpace(1)
|
||||
}
|
||||
|
||||
sz := cap(b.Buf) - len(b.Buf)
|
||||
if sz > len(data) {
|
||||
sz = len(data)
|
||||
}
|
||||
|
||||
b.Buf = append(b.Buf, data[:sz]...)
|
||||
data = data[sz:]
|
||||
}
|
||||
}
|
||||
|
||||
// Size computes the size of a buffer by adding sizes of every chunk.
|
||||
func (b *Buffer) Size() int {
|
||||
size := len(b.Buf)
|
||||
for _, buf := range b.bufs {
|
||||
size += len(buf)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// DumpTo outputs the contents of a buffer to a writer and resets the buffer.
|
||||
func (b *Buffer) DumpTo(w io.Writer) (written int, err error) {
|
||||
var n int
|
||||
for _, buf := range b.bufs {
|
||||
if err != nil {
|
||||
n, err = w.Write(buf)
|
||||
written += n
|
||||
}
|
||||
putBuf(buf)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
n, err = w.Write(b.Buf)
|
||||
written += n
|
||||
}
|
||||
putBuf(b.toPool)
|
||||
|
||||
b.bufs = nil
|
||||
b.Buf = nil
|
||||
b.toPool = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BuildBytes creates a single byte slice with all the contents of the buffer. Data is
|
||||
// copied if it does not fit in a single chunk.
|
||||
func (b *Buffer) BuildBytes() []byte {
|
||||
if len(b.bufs) == 0 {
|
||||
|
||||
ret := b.Buf
|
||||
b.toPool = nil
|
||||
b.Buf = nil
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
ret := make([]byte, 0, b.Size())
|
||||
for _, buf := range b.bufs {
|
||||
ret = append(ret, buf...)
|
||||
putBuf(buf)
|
||||
}
|
||||
|
||||
ret = append(ret, b.Buf...)
|
||||
putBuf(b.toPool)
|
||||
|
||||
b.bufs = nil
|
||||
b.toPool = nil
|
||||
b.Buf = nil
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mailru/easyjson/bootstrap"
|
||||
"github.com/mailru/easyjson/parser"
|
||||
)
|
||||
|
||||
var buildTags = flag.String("build_tags", "", "build tags to add to generated file")
|
||||
var snakeCase = flag.Bool("snake_case", false, "use snake_case names instead of CamelCase by default")
|
||||
var omitEmpty = flag.Bool("omit_empty", false, "omit empty fields by default")
|
||||
var allStructs = flag.Bool("all", false, "generate un-/marshallers for all structs in a file")
|
||||
var leaveTemps = flag.Bool("leave_temps", false, "do not delete temporary files")
|
||||
var stubs = flag.Bool("stubs", false, "only generate stubs for marshallers/unmarshallers methods")
|
||||
|
||||
func generate(fname string) (err error) {
|
||||
p := parser.Parser{AllStructs: *allStructs}
|
||||
if err := p.Parse(fname); err != nil {
|
||||
return fmt.Errorf("Error parsing %v: %v", fname, err)
|
||||
}
|
||||
|
||||
var outName string
|
||||
if s := strings.TrimSuffix(fname, ".go"); s == fname {
|
||||
return fmt.Errorf("Filename must end in '.go'")
|
||||
} else {
|
||||
outName = s + "_easyjson.go"
|
||||
}
|
||||
|
||||
g := bootstrap.Generator{
|
||||
BuildTags: *buildTags,
|
||||
PkgPath: p.PkgPath,
|
||||
PkgName: p.PkgName,
|
||||
Types: p.StructNames,
|
||||
SnakeCase: *snakeCase,
|
||||
OmitEmpty: *omitEmpty,
|
||||
LeaveTemps: *leaveTemps,
|
||||
OutName: outName,
|
||||
StubsOnly: *stubs,
|
||||
}
|
||||
|
||||
if err := g.Run(); err != nil {
|
||||
return fmt.Errorf("Bootstrap failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
files := flag.Args()
|
||||
if len(files) == 0 {
|
||||
files = []string{os.Getenv("GOFILE")}
|
||||
}
|
||||
|
||||
for _, fname := range files {
|
||||
if err := generate(fname); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
)
|
||||
|
||||
// Target this byte size for initial slice allocation to reduce garbage collection.
|
||||
const minSliceBytes = 64
|
||||
|
||||
func (g *Generator) getStructDecoderName(t reflect.Type) string {
|
||||
return g.functionName("easyjson_decode_", t)
|
||||
}
|
||||
|
||||
var primitiveDecoders = map[reflect.Kind]string{
|
||||
reflect.String: "in.String()",
|
||||
reflect.Bool: "in.Bool()",
|
||||
reflect.Int: "in.Int()",
|
||||
reflect.Int8: "in.Int8()",
|
||||
reflect.Int16: "in.Int16()",
|
||||
reflect.Int32: "in.Int32()",
|
||||
reflect.Int64: "in.Int64()",
|
||||
reflect.Uint: "in.Uint()",
|
||||
reflect.Uint8: "in.Uint8()",
|
||||
reflect.Uint16: "in.Uint16()",
|
||||
reflect.Uint32: "in.Uint32()",
|
||||
reflect.Uint64: "in.Uint64()",
|
||||
reflect.Float32: "in.Float32()",
|
||||
reflect.Float64: "in.Float64()",
|
||||
}
|
||||
|
||||
func (g *Generator) genTypeDecoder(t reflect.Type, out string, indent int) error {
|
||||
ws := strings.Repeat(" ", indent)
|
||||
|
||||
unmarshalerIface := reflect.TypeOf((*easyjson.Unmarshaler)(nil)).Elem()
|
||||
if reflect.PtrTo(t).Implements(unmarshalerIface) {
|
||||
fmt.Fprintln(g.out, ws+"("+out+").UnmarshalEasyJSON(in)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check whether type is primitive, needs to be done after interface check.
|
||||
if dec := primitiveDecoders[t.Kind()]; dec != "" {
|
||||
fmt.Fprintln(g.out, ws+out+" = "+dec)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
tmpVar := g.uniqueVarName()
|
||||
elem := t.Elem()
|
||||
|
||||
capacity := minSliceBytes / elem.Size()
|
||||
if capacity == 0 {
|
||||
capacity = 1
|
||||
}
|
||||
|
||||
fmt.Fprintln(g.out, ws+"in.Delim('[')")
|
||||
fmt.Fprintln(g.out, ws+"if !in.IsDelim(']') {")
|
||||
fmt.Fprintln(g.out, ws+" "+out+" = make([]"+g.getType(elem)+", 0, "+fmt.Sprint(capacity)+")")
|
||||
fmt.Fprintln(g.out, ws+"} else {")
|
||||
fmt.Fprintln(g.out, ws+" "+out+" = nil")
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
fmt.Fprintln(g.out, ws+"for !in.IsDelim(']') {")
|
||||
fmt.Fprintln(g.out, ws+" var "+tmpVar+" "+g.getType(elem))
|
||||
|
||||
g.genTypeDecoder(elem, tmpVar, indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+" "+out+" = append("+out+", "+tmpVar+")")
|
||||
fmt.Fprintln(g.out, ws+" in.WantComma()")
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
fmt.Fprintln(g.out, ws+"in.Delim(']')")
|
||||
|
||||
case reflect.Struct:
|
||||
dec := g.getStructDecoderName(t)
|
||||
g.addType(t)
|
||||
|
||||
fmt.Fprintln(g.out, ws+dec+"(in, &"+out+")")
|
||||
|
||||
case reflect.Ptr:
|
||||
fmt.Fprintln(g.out, ws+"if in.IsNull() {")
|
||||
fmt.Fprintln(g.out, ws+" in.Skip()")
|
||||
fmt.Fprintln(g.out, ws+" "+out+" = nil")
|
||||
fmt.Fprintln(g.out, ws+"} else {")
|
||||
fmt.Fprintln(g.out, ws+" "+out+" = new("+g.getType(t.Elem())+")")
|
||||
|
||||
g.genTypeDecoder(t.Elem(), "*"+out, indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
|
||||
case reflect.Map:
|
||||
key := t.Key()
|
||||
if key.Kind() != reflect.String {
|
||||
return fmt.Errorf("map type %v not supported: only string keys are allowed", key)
|
||||
}
|
||||
elem := t.Elem()
|
||||
tmpVar := g.uniqueVarName()
|
||||
|
||||
fmt.Fprintln(g.out, ws+"in.Delim('{')")
|
||||
fmt.Fprintln(g.out, ws+"if !in.IsDelim('}') {")
|
||||
fmt.Fprintln(g.out, ws+out+" = make("+g.getType(t)+")")
|
||||
fmt.Fprintln(g.out, ws+"} else {")
|
||||
fmt.Fprintln(g.out, ws+out+" = nil")
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
|
||||
fmt.Fprintln(g.out, ws+"for !in.IsDelim('}') {")
|
||||
fmt.Fprintln(g.out, ws+" key := in.String()")
|
||||
fmt.Fprintln(g.out, ws+" in.WantColon()")
|
||||
fmt.Fprintln(g.out, ws+" var "+tmpVar+" "+g.getType(elem))
|
||||
|
||||
g.genTypeDecoder(elem, tmpVar, indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+" ("+out+")[key] = "+tmpVar)
|
||||
fmt.Fprintln(g.out, ws+" in.WantComma()")
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
fmt.Fprintln(g.out, ws+"in.Delim('}')")
|
||||
|
||||
case reflect.Interface:
|
||||
if t.NumMethod() != 0 {
|
||||
return fmt.Errorf("interface type %v not supported: only interface{} is allowed", t)
|
||||
}
|
||||
fmt.Fprintln(g.out, ws+out+" = in.Interface()")
|
||||
|
||||
default:
|
||||
return fmt.Errorf("don't know how to decode %v", t)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (g *Generator) genStructFieldDecoder(t reflect.Type, f reflect.StructField) error {
|
||||
jsonName := g.namer.GetJSONFieldName(t, f)
|
||||
|
||||
fmt.Fprintf(g.out, " case %q:\n", jsonName)
|
||||
return g.genTypeDecoder(f.Type, "out."+f.Name, 3)
|
||||
}
|
||||
|
||||
func mergeStructFields(fields1, fields2 []reflect.StructField) (fields []reflect.StructField) {
|
||||
used := map[string]bool{}
|
||||
for _, f := range fields2 {
|
||||
used[f.Name] = true
|
||||
fields = append(fields, f)
|
||||
}
|
||||
|
||||
for _, f := range fields1 {
|
||||
if !used[f.Name] {
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getStructFields(t reflect.Type) []reflect.StructField {
|
||||
var efields []reflect.StructField
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
efields = mergeStructFields(efields, getStructFields(f.Type))
|
||||
}
|
||||
|
||||
var fields []reflect.StructField
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
c := []rune(f.Name)[0]
|
||||
if unicode.IsUpper(c) {
|
||||
fields = append(fields, f)
|
||||
}
|
||||
}
|
||||
return mergeStructFields(efields, fields)
|
||||
}
|
||||
|
||||
func (g *Generator) genStructDecoder(t reflect.Type) error {
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot generate encoder/decoder for %v, not a struct type", t)
|
||||
}
|
||||
|
||||
fname := g.getStructDecoderName(t)
|
||||
typ := g.getType(t)
|
||||
|
||||
fmt.Fprintln(g.out, "func "+fname+"(in *jlexer.Lexer, out *"+typ+") {")
|
||||
fmt.Fprintln(g.out, " in.Delim('{')")
|
||||
fmt.Fprintln(g.out, " for !in.IsDelim('}') {")
|
||||
fmt.Fprintln(g.out, " key := in.UnsafeString()")
|
||||
fmt.Fprintln(g.out, " in.WantColon()")
|
||||
fmt.Fprintln(g.out, " if in.IsNull() {")
|
||||
fmt.Fprintln(g.out, " in.Skip()")
|
||||
fmt.Fprintln(g.out, " in.WantComma()")
|
||||
fmt.Fprintln(g.out, " continue")
|
||||
fmt.Fprintln(g.out, " }")
|
||||
fmt.Fprintln(g.out, " switch key {")
|
||||
|
||||
for _, f := range getStructFields(t) {
|
||||
if err := g.genStructFieldDecoder(t, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(g.out, " default:")
|
||||
fmt.Fprintln(g.out, " in.SkipRecursive()")
|
||||
fmt.Fprintln(g.out, " }")
|
||||
fmt.Fprintln(g.out, " in.WantComma()")
|
||||
fmt.Fprintln(g.out, " }")
|
||||
fmt.Fprintln(g.out, " in.Delim('}')")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Generator) genStructUnmarshaller(t reflect.Type) error {
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot generate encoder/decoder for %v, not a struct type", t)
|
||||
}
|
||||
|
||||
fname := g.getStructDecoderName(t)
|
||||
typ := g.getType(t)
|
||||
|
||||
fmt.Fprintln(g.out, "func (v *"+typ+") UnmarshalJSON(data []byte) error {")
|
||||
fmt.Fprintln(g.out, " r := jlexer.Lexer{Data: data}")
|
||||
fmt.Fprintln(g.out, " "+fname+"(&r, v)")
|
||||
fmt.Fprintln(g.out, " return r.Error()")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
fmt.Fprintln(g.out, "func (v *"+typ+") UnmarshalEasyJSON(l *jlexer.Lexer) {")
|
||||
fmt.Fprintln(g.out, " "+fname+"(l, v)")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
|
||||
return nil
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
)
|
||||
|
||||
func (g *Generator) getStructEncoderName(t reflect.Type) string {
|
||||
return g.functionName("easyjson_encode_", t)
|
||||
}
|
||||
|
||||
var primitiveEncoders = map[reflect.Kind]string{
|
||||
reflect.String: "out.String",
|
||||
reflect.Bool: "out.Bool",
|
||||
reflect.Int: "out.Int",
|
||||
reflect.Int8: "out.Int8",
|
||||
reflect.Int16: "out.Int16",
|
||||
reflect.Int32: "out.Int32",
|
||||
reflect.Int64: "out.Int64",
|
||||
reflect.Uint: "out.Uint",
|
||||
reflect.Uint8: "out.Uint8",
|
||||
reflect.Uint16: "out.Uint16",
|
||||
reflect.Uint32: "out.Uint32",
|
||||
reflect.Uint64: "out.Uint64",
|
||||
reflect.Float32: "out.Float32",
|
||||
reflect.Float64: "out.Float64",
|
||||
}
|
||||
|
||||
func (g *Generator) genTypeEncoder(t reflect.Type, in string, indent int) error {
|
||||
ws := strings.Repeat(" ", indent)
|
||||
|
||||
marshalerIface := reflect.TypeOf((*easyjson.Marshaler)(nil)).Elem()
|
||||
if reflect.PtrTo(t).Implements(marshalerIface) {
|
||||
fmt.Fprintln(g.out, ws+"("+in+").MarshalEasyJSON(out)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check whether type is primitive, needs to be done after interface check.
|
||||
if enc := primitiveEncoders[t.Kind()]; enc != "" {
|
||||
fmt.Fprintln(g.out, ws+enc+"("+in+")")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
elem := t.Elem()
|
||||
iVar := g.uniqueVarName()
|
||||
vVar := g.uniqueVarName()
|
||||
|
||||
fmt.Fprintln(g.out, ws+"out.RawByte('[')")
|
||||
fmt.Fprintln(g.out, ws+"for "+iVar+", "+vVar+" := range "+in+" {")
|
||||
fmt.Fprintln(g.out, ws+" if "+iVar+" > 0 {")
|
||||
fmt.Fprintln(g.out, ws+" out.RawByte(',')")
|
||||
fmt.Fprintln(g.out, ws+" }")
|
||||
|
||||
g.genTypeEncoder(elem, vVar, indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
fmt.Fprintln(g.out, ws+"out.RawByte(']')")
|
||||
|
||||
case reflect.Struct:
|
||||
enc := g.getStructEncoderName(t)
|
||||
g.addType(t)
|
||||
|
||||
fmt.Fprintln(g.out, ws+enc+"(out, &"+in+")")
|
||||
|
||||
case reflect.Ptr:
|
||||
fmt.Fprintln(g.out, ws+"if "+in+" == nil {")
|
||||
fmt.Fprintln(g.out, ws+` out.RawString("null")`)
|
||||
fmt.Fprintln(g.out, ws+"} else {")
|
||||
|
||||
g.genTypeEncoder(t.Elem(), "*"+in, indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
|
||||
case reflect.Map:
|
||||
key := t.Key()
|
||||
if key.Kind() != reflect.String {
|
||||
return fmt.Errorf("map type %v not supported: only string keys are allowed", key)
|
||||
}
|
||||
tmpVar := g.uniqueVarName()
|
||||
|
||||
fmt.Fprintln(g.out, ws+"out.RawByte('{')")
|
||||
fmt.Fprintln(g.out, ws+tmpVar+"_first := true")
|
||||
fmt.Fprintln(g.out, ws+"for "+tmpVar+"_name, "+tmpVar+"_value := range "+in+" {")
|
||||
fmt.Fprintln(g.out, ws+" if !"+tmpVar+"_first { out.RawByte(',') }")
|
||||
fmt.Fprintln(g.out, ws+" "+tmpVar+"_first = false")
|
||||
fmt.Fprintln(g.out, ws+" out.String("+tmpVar+"_name)")
|
||||
|
||||
g.genTypeEncoder(t.Elem(), tmpVar+"_value", indent+1)
|
||||
|
||||
fmt.Fprintln(g.out, ws+"}")
|
||||
fmt.Fprintln(g.out, ws+"out.RawByte('}')")
|
||||
|
||||
case reflect.Interface:
|
||||
if t.NumMethod() != 0 {
|
||||
return fmt.Errorf("interface type %v not supported: only interface{} is allowed", t)
|
||||
}
|
||||
fmt.Fprintln(g.out, ws+"out.Raw(json.Marshal("+in+"))")
|
||||
|
||||
default:
|
||||
return fmt.Errorf("don't know how to encode %v", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Generator) notEmptyCheck(t reflect.Type, v string) string {
|
||||
optionalIface := reflect.TypeOf((*easyjson.Optional)(nil)).Elem()
|
||||
if reflect.PtrTo(t).Implements(optionalIface) {
|
||||
return "(" + v + ").IsDefined()"
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return "len(" + v + ") != 0"
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v + " != nil"
|
||||
case reflect.Bool:
|
||||
return v
|
||||
case reflect.String:
|
||||
return v + ` != ""`
|
||||
case reflect.Float32, reflect.Float64,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
|
||||
return v + " != 0"
|
||||
|
||||
default:
|
||||
return "true"
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Generator) genStructFieldEncoder(t reflect.Type, f reflect.StructField) error {
|
||||
jsonName := g.namer.GetJSONFieldName(t, f)
|
||||
omitEmpty := g.omitEmpty
|
||||
|
||||
for i, s := range strings.Split(f.Tag.Get("json"), ",") {
|
||||
if i > 0 && s == "omitempty" {
|
||||
omitEmpty = true
|
||||
} else if i > 0 && s == "!omitempty" {
|
||||
omitEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
if !omitEmpty {
|
||||
fmt.Fprintln(g.out, " if !first { out.RawByte(',') }")
|
||||
fmt.Fprintln(g.out, " first = false")
|
||||
fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":")
|
||||
return g.genTypeEncoder(f.Type, "in."+f.Name, 1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(g.out, " if", g.notEmptyCheck(f.Type, "in."+f.Name), "{")
|
||||
fmt.Fprintln(g.out, " if !first { out.RawByte(',') }")
|
||||
fmt.Fprintln(g.out, " first = false")
|
||||
|
||||
fmt.Fprintf(g.out, " out.RawString(%q)\n", strconv.Quote(jsonName)+":")
|
||||
if err := g.genTypeEncoder(f.Type, "in."+f.Name, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(g.out, " }")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Generator) genStructEncoder(t reflect.Type) error {
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot generate encoder/decoder for %v, not a struct type", t)
|
||||
}
|
||||
|
||||
fname := g.getStructEncoderName(t)
|
||||
typ := g.getType(t)
|
||||
|
||||
fmt.Fprintln(g.out, "func "+fname+"(out *jwriter.Writer, in *"+typ+") {")
|
||||
fmt.Fprintln(g.out, " out.RawByte('{')")
|
||||
fmt.Fprintln(g.out, " first := true")
|
||||
fmt.Fprintln(g.out, " _ = first")
|
||||
|
||||
for _, f := range getStructFields(t) {
|
||||
if err := g.genStructFieldEncoder(t, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintln(g.out, " out.RawByte('}')")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Generator) genStructMarshaller(t reflect.Type) error {
|
||||
if t.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot generate encoder/decoder for %v, not a struct type", t)
|
||||
}
|
||||
|
||||
fname := g.getStructEncoderName(t)
|
||||
typ := g.getType(t)
|
||||
|
||||
fmt.Fprintln(g.out, "func (v *"+typ+") MarshalJSON() ([]byte, error) {")
|
||||
fmt.Fprintln(g.out, " w := jwriter.Writer{}")
|
||||
fmt.Fprintln(g.out, " "+fname+"(&w, v)")
|
||||
fmt.Fprintln(g.out, " return w.Buffer.BuildBytes(), w.Error")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
|
||||
fmt.Fprintln(g.out, "func (v *"+typ+") MarshalEasyJSON(w *jwriter.Writer) {")
|
||||
fmt.Fprintln(g.out, " "+fname+"(w, v)")
|
||||
fmt.Fprintln(g.out, "}")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const pkgWriter = "github.com/mailru/easyjson/jwriter"
|
||||
const pkgLexer = "github.com/mailru/easyjson/jlexer"
|
||||
|
||||
// FieldNamer defines a policy for generating names for struct fields.
|
||||
type FieldNamer interface {
|
||||
GetJSONFieldName(t reflect.Type, f reflect.StructField) string
|
||||
}
|
||||
|
||||
// Generator generates the requested marshallers/unmarshallers.
|
||||
type Generator struct {
|
||||
out *bytes.Buffer
|
||||
|
||||
pkgName string
|
||||
pkgPath string
|
||||
buildTags string
|
||||
|
||||
varCounter int
|
||||
|
||||
omitEmpty bool
|
||||
namer FieldNamer
|
||||
|
||||
// package path to local alias map for tracking imports
|
||||
imports map[string]string
|
||||
|
||||
// types that marshallers were requested for by user
|
||||
marshallers map[reflect.Type]bool
|
||||
|
||||
// types that encoders were already generated for
|
||||
typesSeen map[reflect.Type]bool
|
||||
|
||||
// types that encoders were requested for (e.g. by encoders of other types)
|
||||
typesUnseen []reflect.Type
|
||||
|
||||
// function name to relevant type maps to track names of de-/encoders in
|
||||
// case of a name clash or unnamed structs
|
||||
functionNames map[string]reflect.Type
|
||||
}
|
||||
|
||||
// NewGenerator initializes and returns a Generator.
|
||||
func NewGenerator() *Generator {
|
||||
return &Generator{
|
||||
imports: map[string]string{
|
||||
pkgWriter: "jwriter",
|
||||
pkgLexer: "jlexer",
|
||||
"encoding/json": "json",
|
||||
},
|
||||
namer: DefaultFieldNamer{},
|
||||
marshallers: make(map[reflect.Type]bool),
|
||||
typesSeen: make(map[reflect.Type]bool),
|
||||
functionNames: make(map[string]reflect.Type),
|
||||
}
|
||||
}
|
||||
|
||||
// SetPkg sets the name and path of output package.
|
||||
func (g *Generator) SetPkg(name, path string) {
|
||||
g.pkgName = name
|
||||
g.pkgPath = path
|
||||
}
|
||||
|
||||
// SetBuildTags sets build tags for the output file.
|
||||
func (g *Generator) SetBuildTags(tags string) {
|
||||
g.buildTags = tags
|
||||
}
|
||||
|
||||
// SetFieldNamer sets field naming strategy.
|
||||
func (g *Generator) SetFieldNamer(n FieldNamer) {
|
||||
g.namer = n
|
||||
}
|
||||
|
||||
// UseSnakeCase sets snake_case field naming strategy.
|
||||
func (g *Generator) UseSnakeCase() {
|
||||
g.namer = SnakeCaseFieldNamer{}
|
||||
}
|
||||
|
||||
// OmitEmpty triggers `json=",omitempty"` behaviour by default.
|
||||
func (g *Generator) OmitEmpty() {
|
||||
g.omitEmpty = true
|
||||
}
|
||||
|
||||
// addTypes requests to generate en-/decoding functions for the given type.
|
||||
func (g *Generator) addType(t reflect.Type) {
|
||||
if g.typesSeen[t] {
|
||||
return
|
||||
}
|
||||
for _, t1 := range g.typesUnseen {
|
||||
if t1 == t {
|
||||
return
|
||||
}
|
||||
}
|
||||
g.typesUnseen = append(g.typesUnseen, t)
|
||||
}
|
||||
|
||||
// Add requests to generate (un-)marshallers and en-/decoding functions for the type of given object.
|
||||
func (g *Generator) Add(obj interface{}) {
|
||||
g.addType(reflect.TypeOf(obj))
|
||||
g.marshallers[reflect.TypeOf(obj)] = true
|
||||
}
|
||||
|
||||
// printHeader prints package declaration and imports.
|
||||
func (g *Generator) printHeader() {
|
||||
if g.buildTags != "" {
|
||||
fmt.Println("// +build ", g.buildTags)
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println("package ", g.pkgName)
|
||||
fmt.Println()
|
||||
|
||||
byAlias := map[string]string{}
|
||||
var aliases []string
|
||||
for path, alias := range g.imports {
|
||||
aliases = append(aliases, alias)
|
||||
byAlias[alias] = path
|
||||
}
|
||||
|
||||
sort.Strings(aliases)
|
||||
fmt.Println("import (")
|
||||
for _, alias := range g.imports {
|
||||
fmt.Printf(" %s %q\n", alias, byAlias[alias])
|
||||
}
|
||||
|
||||
fmt.Println(")")
|
||||
fmt.Println("")
|
||||
fmt.Println("var _ = json.RawMessage{} // suppress unused package warning")
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Run runs the generator and outputs generated code to out.
|
||||
func (g *Generator) Run(out io.Writer) error {
|
||||
g.out = &bytes.Buffer{}
|
||||
|
||||
for len(g.typesUnseen) > 0 {
|
||||
t := g.typesUnseen[len(g.typesUnseen)-1]
|
||||
g.typesUnseen = g.typesUnseen[:len(g.typesUnseen)-1]
|
||||
g.typesSeen[t] = true
|
||||
|
||||
if err := g.genStructDecoder(t); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.genStructEncoder(t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !g.marshallers[t] {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := g.genStructMarshaller(t); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.genStructUnmarshaller(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
g.printHeader()
|
||||
_, err := out.Write(g.out.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// pkgAlias creates and returns and import alias for a given package.
|
||||
func (g *Generator) pkgAlias(pkgPath string) string {
|
||||
if alias := g.imports[pkgPath]; alias != "" {
|
||||
return alias
|
||||
}
|
||||
|
||||
for i := 0; ; i++ {
|
||||
alias := path.Base(pkgPath)
|
||||
if i > 0 {
|
||||
alias += fmt.Sprint(i)
|
||||
}
|
||||
|
||||
exists := false
|
||||
for _, v := range g.imports {
|
||||
if v == alias {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
g.imports[pkgPath] = alias
|
||||
return alias
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getType return the textual type name of given type that can be used in generated code.
|
||||
func (g *Generator) getType(t reflect.Type) string {
|
||||
if t.Name() == "" || t.PkgPath() == "" {
|
||||
return t.String()
|
||||
} else if t.PkgPath() == g.pkgPath {
|
||||
return t.Name()
|
||||
}
|
||||
// TODO: unnamed structs.
|
||||
return g.pkgAlias(t.PkgPath()) + "." + t.Name()
|
||||
}
|
||||
|
||||
// uniqueVarName returns a file-unique name that can be used for generated variables.
|
||||
func (g *Generator) uniqueVarName() string {
|
||||
g.varCounter++
|
||||
return fmt.Sprint("v", g.varCounter)
|
||||
}
|
||||
|
||||
// safeName escapes unsafe characters in pkg/type name and returns a string that can be used
|
||||
// in encoder/decoder names for the type.
|
||||
func safeName(t reflect.Type) string {
|
||||
name := t.PkgPath()
|
||||
if t.Name() == "" {
|
||||
name += "anonymous"
|
||||
} else {
|
||||
name += "." + t.Name()
|
||||
}
|
||||
|
||||
var ret []rune
|
||||
for _, c := range name {
|
||||
if unicode.IsLetter(c) || unicode.IsDigit(c) {
|
||||
ret = append(ret, c)
|
||||
} else {
|
||||
ret = append(ret, '_')
|
||||
}
|
||||
}
|
||||
return string(ret)
|
||||
}
|
||||
|
||||
// functionName returns a function name for a given type with a given prefix. If a function
|
||||
// with this prefix already exists for a type, it is returned.
|
||||
//
|
||||
// Method is used to track encoder/decoder names for the type.
|
||||
func (g *Generator) functionName(prefix string, t reflect.Type) string {
|
||||
name := prefix + safeName(t)
|
||||
|
||||
// Most of the names will be unique, try a shortcut first.
|
||||
if e, ok := g.functionNames[name]; !ok || e == t {
|
||||
g.functionNames[name] = t
|
||||
return name
|
||||
}
|
||||
|
||||
// Search if the function already exists.
|
||||
for name1, t1 := range g.functionNames {
|
||||
if t1 == t && strings.HasPrefix(name1, prefix) {
|
||||
return name1
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new name in the case of a clash.
|
||||
for i := 1; ; i++ {
|
||||
nm := fmt.Sprint(name, i)
|
||||
if _, ok := g.functionNames[nm]; ok {
|
||||
continue
|
||||
}
|
||||
g.functionNames[nm] = t
|
||||
return nm
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultFieldsNamer implements trivial naming policy equivalent to encoding/json.
|
||||
type DefaultFieldNamer struct{}
|
||||
|
||||
func (DefaultFieldNamer) GetJSONFieldName(t reflect.Type, f reflect.StructField) string {
|
||||
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
|
||||
if jsonName != "" {
|
||||
return jsonName
|
||||
} else {
|
||||
return f.Name
|
||||
}
|
||||
}
|
||||
|
||||
// SnakeCaseFieldNamer implements CamelCase to snake_case conversion for fields names.
|
||||
type SnakeCaseFieldNamer struct{}
|
||||
|
||||
func camelToSnake(name string) string {
|
||||
var ret bytes.Buffer
|
||||
|
||||
multipleUpper := false
|
||||
var lastUpper rune
|
||||
|
||||
for _, c := range name {
|
||||
// Non-lowercase character after uppercase is considered to be uppercase too.
|
||||
isUpper := (unicode.IsUpper(c) || (lastUpper != 0 && !unicode.IsLower(c)))
|
||||
|
||||
if lastUpper != 0 {
|
||||
// Output a delimiter if last character was either the first uppercase character
|
||||
// in a row, or the last one in a row (e.g. 'S' in "HTTPServer").
|
||||
// Do not output a delimiter at the beginning of the name.
|
||||
|
||||
firstInRow := !multipleUpper
|
||||
lastInRow := !isUpper
|
||||
|
||||
if ret.Len() > 0 && (firstInRow || lastInRow) {
|
||||
ret.WriteByte('_')
|
||||
}
|
||||
ret.WriteRune(unicode.ToLower(lastUpper))
|
||||
}
|
||||
|
||||
// Buffer uppercase char, do not output it yet as a delimiter may be required if the
|
||||
// next character is lowercase.
|
||||
if isUpper {
|
||||
multipleUpper = (lastUpper != 0)
|
||||
lastUpper = c
|
||||
continue
|
||||
}
|
||||
|
||||
ret.WriteRune(c)
|
||||
lastUpper = 0
|
||||
multipleUpper = false
|
||||
}
|
||||
|
||||
if lastUpper != 0 {
|
||||
ret.WriteRune(unicode.ToLower(lastUpper))
|
||||
}
|
||||
return string(ret.Bytes())
|
||||
}
|
||||
|
||||
func (SnakeCaseFieldNamer) GetJSONFieldName(t reflect.Type, f reflect.StructField) string {
|
||||
jsonName := strings.Split(f.Tag.Get("json"), ",")[0]
|
||||
if jsonName != "" {
|
||||
return jsonName
|
||||
}
|
||||
|
||||
return camelToSnake(f.Name)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package gen
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCamelToSnake(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
In, Out string
|
||||
}{
|
||||
{"", ""},
|
||||
{"A", "a"},
|
||||
{"SimpleExample", "simple_example"},
|
||||
{"internalField", "internal_field"},
|
||||
|
||||
{"SomeHTTPStuff", "some_http_stuff"},
|
||||
{"WriteJSON", "write_json"},
|
||||
{"HTTP2Server", "http2_server"},
|
||||
|
||||
{"JSONHTTPRPCServer", "jsonhttprpc_server"}, // nothing can be done here without a dictionary
|
||||
} {
|
||||
got := camelToSnake(test.In)
|
||||
if got != test.Out {
|
||||
t.Errorf("[%d] camelToSnake(%s) = %s; want %s", i, test.In, got, test.Out)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// Package easyjson contains marshaler/unmarshaler interfaces and helper functions.
|
||||
package easyjson
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"github.com/mailru/easyjson/jlexer"
|
||||
"github.com/mailru/easyjson/jwriter"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Marshaler is an easyjson-compatible marshaler interface.
|
||||
type Marshaler interface {
|
||||
MarshalEasyJSON(w *jwriter.Writer)
|
||||
}
|
||||
|
||||
// Marshaler is an easyjson-compatible unmarshaler interface.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalEasyJSON(w *jlexer.Lexer)
|
||||
}
|
||||
|
||||
// Optional defines an undefined-test method for a type to integrate with 'omitempty' logic.
|
||||
type Optional interface {
|
||||
IsDefined() bool
|
||||
}
|
||||
|
||||
// Marshal returns data as a single byte slice. Method is suboptimal as the data is likely to be copied
|
||||
// from a chain of smaller chunks.
|
||||
func Marshal(v Marshaler) ([]byte, error) {
|
||||
w := jwriter.Writer{}
|
||||
v.MarshalEasyJSON(&w)
|
||||
return w.BuildBytes()
|
||||
}
|
||||
|
||||
// MarshalToWriter marshals the data to an io.Writer.
|
||||
func MarshalToWriter(v Marshaler, w io.Writer) (written int, err error) {
|
||||
jw := jwriter.Writer{}
|
||||
v.MarshalEasyJSON(&jw)
|
||||
return jw.DumpTo(w)
|
||||
}
|
||||
|
||||
// MarshalToHTTPResponseWriter sets Content-Length and Content-Type headers for the
|
||||
// http.ResponseWriter, and send the data to the writer. started will be equal to
|
||||
// false if an error occurred before any http.ResponseWriter methods were actually
|
||||
// invoked (in this case a 500 reply is possible).
|
||||
func MarshalToHTTPResponseWriter(v Marshaler, w http.ResponseWriter) (started bool, written int, err error) {
|
||||
jw := jwriter.Writer{}
|
||||
v.MarshalEasyJSON(&jw)
|
||||
if jw.Error != nil {
|
||||
return false, 0, jw.Error
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(jw.Size()))
|
||||
|
||||
started = true
|
||||
written, err = jw.DumpTo(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal decodes the JSON in data into the object.
|
||||
func Unmarshal(v Unmarshaler, data []byte) error {
|
||||
l := jlexer.Lexer{Data: data}
|
||||
v.UnmarshalEasyJSON(&l)
|
||||
return l.Error()
|
||||
}
|
||||
|
||||
// UnmarshalFromReader reads all the data in the reader and decodes as JSON into the object.
|
||||
func UnmarshalFromReader(v Unmarshaler, r io.Reader) error {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l := jlexer.Lexer{Data: data}
|
||||
v.UnmarshalEasyJSON(&l)
|
||||
return l.Error()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user