diff --git a/pkg/json/marshal.go b/pkg/json/marshal.go index a22f9fd..4e7c662 100644 --- a/pkg/json/marshal.go +++ b/pkg/json/marshal.go @@ -108,7 +108,7 @@ func appendUint(v reflect.Value, buf []byte) []byte { } func appendFloat(v reflect.Value, buf []byte) []byte { - return strconv.AppendFloat(buf, v.Float(), 'f', 2, 64) + return strconv.AppendFloat(buf, v.Float(), 'f', -1, 64) } func appendWithCustomMarshaler(v reflect.Value, buf []byte) (res []byte, ok bool) { diff --git a/pkg/json/marshal_test.go b/pkg/json/marshal_test.go index 02d1599..088b98d 100644 --- a/pkg/json/marshal_test.go +++ b/pkg/json/marshal_test.go @@ -3,23 +3,17 @@ package json_test import ( stdJSON "encoding/json" "fmt" - "maps" "reflect" - "runtime/debug" "strconv" "testing" "github.com/bytedance/sonic" "github.com/stretchr/testify/require" "github.com/yusing/go-proxy/internal/utils/strutils" + . "github.com/yusing/go-proxy/internal/utils/testing" . "github.com/yusing/go-proxy/pkg/json" ) -func init() { - debug.SetMemoryLimit(1024 * 1024) - debug.SetMaxStack(1024 * 1024) -} - type testStruct struct { Name string `json:"name"` Age int `json:"age"` @@ -104,19 +98,6 @@ type selfReferencing struct { Self *selfReferencing `json:"self"` } -var testData = map[string]any{ - "string": "test string", - "number": 42, - "float": 3.14159, - "bool": true, - "null_value": nil, - "array": []any{1, "2", 3.3, true, false, nil}, - "object": map[string]any{ - "nested": "value", - "count": 10, - }, -} - func TestMarshal(t *testing.T) { tests := []struct { name string @@ -386,8 +367,19 @@ func TestMarshal(t *testing.T) { input: map[string]testStruct{"one": {Name: "John", Age: 30, Score: 8.5}, "two": {Name: "Jane", Age: 25, Score: 9.5}}, }, { - name: "complex_map", - input: testData, + name: "complex_map", + input: map[string]any{ + "string": "test string", + "number": 42, + "float": 3.14159, + "bool": true, + "null_value": nil, + "array": []any{1, "2", 3.3, true, false, nil}, + "object": map[string]any{ + "nested": "value", + "count": 10, + }, + }, }, } @@ -411,34 +403,6 @@ func TestMarshal(t *testing.T) { } } -func TestMapAndMapSlice(t *testing.T) { - tests := []struct { - name string - input any - expected string - }{ - { - name: "Map", - input: Map[string]{"key1": "value1", "key2": "value2"}, - expected: `{"key1":"value1","key2":"value2"}`, - }, - { - name: "MapSlice", - input: MapSlice[string]{{"key1": "value1"}, {"key2": "value2"}}, - expected: `[{"key1":"value1"},{"key2":"value2"}]`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, _ := Marshal(tt.input) - if string(result) != tt.expected { - t.Errorf("Marshal(%v) = %s, want %s", tt.input, string(result), tt.expected) - } - }) - } -} - func TestMarshalSyntacticEquivalence(t *testing.T) { testData := []any{ "test\r\nstring", @@ -482,7 +446,46 @@ func TestMarshalSyntacticEquivalence(t *testing.T) { } } -func BenchmarkMarshalNoStructStdLib(b *testing.B) { +func TestWithTestStruct(t *testing.T) { + var custom, stdlib []byte + var err error + + custom, err = Marshal(TwitterObject) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + stdlib, err = stdJSON.Marshal(TwitterObject) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + var unmarshalCustom, unmarshalStdlib any + if err := Unmarshal(custom, &unmarshalCustom); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if err := sonic.Unmarshal(stdlib, &unmarshalStdlib); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + ExpectEqual(t, unmarshalCustom, unmarshalStdlib) +} + +func BenchmarkMarshalSimpleStdLib(b *testing.B) { + testData := map[string]any{ + "string": "test string", + "number": 42, + "float": 3.14159, + "bool": true, + "null_value": nil, + "bytes": []byte("test"), + "array": []any{1, "2", 3.3, true, false, nil}, + "object": map[string]any{ + "nested": "value", + "count": 10, + }, + } + b.Run("StdLib", func(b *testing.B) { for b.Loop() { _, _ = stdJSON.Marshal(testData) @@ -502,28 +505,22 @@ func BenchmarkMarshalNoStructStdLib(b *testing.B) { }) } -func BenchmarkMarshalStruct(b *testing.B) { - withStruct := maps.Clone(testData) - withStruct["struct1"] = withAnonymous{Anonymous: Anonymous{Value: "one", Value2: 1}} - withStruct["struct2"] = &withPointerAnonymous{Anonymous: &Anonymous{Value: "two", Value2: 2}} - withStruct["struct3"] = &testStruct{Name: "three", Age: 30, Score: 9.8} - b.ResetTimer() - +func BenchmarkMarshalTestStruct(b *testing.B) { b.Run("StdLib", func(b *testing.B) { for b.Loop() { - _, _ = stdJSON.Marshal(withStruct) + _, _ = stdJSON.Marshal(TwitterObject) } }) b.Run("Sonic", func(b *testing.B) { for b.Loop() { - _, _ = sonic.Marshal(withStruct) + _, _ = sonic.Marshal(TwitterObject) } }) b.Run("Custom", func(b *testing.B) { for b.Loop() { - _, _ = Marshal(withStruct) + _, _ = Marshal(TwitterObject) } }) } diff --git a/pkg/json/testdata_test.go b/pkg/json/testdata_test.go new file mode 100644 index 0000000..c1a160a --- /dev/null +++ b/pkg/json/testdata_test.go @@ -0,0 +1,563 @@ +/* + * Copyright 2021 ByteDance Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// from https://github.com/bytedance/sonic/blob/main/encoder/testdata_test.go +package json_test + +import "github.com/bytedance/sonic" + +var TwitterObject = func() *TwitterStruct { + var t TwitterStruct + err := sonic.Unmarshal([]byte(TwitterJson), &t) + if err != nil { + panic(err) + } + return &t +}() + +const TwitterJson = `{ + "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": "https://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": "https://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": "Twitter for Mac", + "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": "https://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": "https://bullcityrecords.com/wnng/", + "indices": [ + 0, + 32 + ] + } + ] + }, + "description": { + "urls": [ + + ] + } + }, + "default_profile": false, + "contributors_enabled": false, + "favourites_count": 8, + "url": "https://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": "https://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": "https://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": "https://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": "https://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": "https://www.omnitarian.me", + "indices": [ + 0, + 24 + ] + } + ] + }, + "description": { + "urls": [ + + ] + } + }, + "default_profile": false, + "contributors_enabled": false, + "favourites_count": 647, + "url": "https://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": "https://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": "Twitter for iPhone", + "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" + } + }` + +type TwitterStruct struct { + Statuses []Statuses `json:"statuses"` + SearchMetadata SearchMetadata `json:"search_metadata"` +} + +type Hashtags struct { + Text string `json:"text"` + Indices []int `json:"indices"` +} + +type Entities struct { + Urls []interface{} `json:"urls"` + Hashtags []Hashtags `json:"hashtags"` + UserMentions []interface{} `json:"user_mentions"` +} + +type Metadata struct { + IsoLanguageCode string `json:"iso_language_code"` + ResultType string `json:"result_type"` +} + +type Urls struct { + ExpandedURL interface{} `json:"expanded_url"` + URL string `json:"url"` + Indices []int `json:"indices"` +} + +type URL struct { + Urls []Urls `json:"urls"` +} + +type Description struct { + Urls []interface{} `json:"urls"` +} + +type UserEntities struct { + URL URL `json:"url"` + Description Description `json:"description"` +} + +type User struct { + ProfileSidebarFillColor string `json:"profile_sidebar_fill_color"` + ProfileSidebarBorderColor string `json:"profile_sidebar_border_color"` + ProfileBackgroundTile bool `json:"profile_background_tile"` + Name string `json:"name"` + ProfileImageURL string `json:"profile_image_url"` + CreatedAt string `json:"created_at"` + Location string `json:"location"` + FollowRequestSent interface{} `json:"follow_request_sent"` + ProfileLinkColor string `json:"profile_link_color"` + IsTranslator bool `json:"is_translator"` + IDStr string `json:"id_str"` + Entities UserEntities `json:"entities"` + DefaultProfile bool `json:"default_profile"` + ContributorsEnabled bool `json:"contributors_enabled"` + FavouritesCount int `json:"favourites_count"` + URL interface{} `json:"url"` + ProfileImageURLHTTPS string `json:"profile_image_url_https"` + UtcOffset int `json:"utc_offset"` + ID int `json:"id"` + ProfileUseBackgroundImage bool `json:"profile_use_background_image"` + ListedCount int `json:"listed_count"` + ProfileTextColor string `json:"profile_text_color"` + Lang string `json:"lang"` + FollowersCount int `json:"followers_count"` + Protected bool `json:"protected"` + Notifications interface{} `json:"notifications"` + ProfileBackgroundImageURLHTTPS string `json:"profile_background_image_url_https"` + ProfileBackgroundColor string `json:"profile_background_color"` + Verified bool `json:"verified"` + GeoEnabled bool `json:"geo_enabled"` + TimeZone string `json:"time_zone"` + Description string `json:"description"` + DefaultProfileImage bool `json:"default_profile_image"` + ProfileBackgroundImageURL string `json:"profile_background_image_url"` + StatusesCount int `json:"statuses_count"` + FriendsCount int `json:"friends_count"` + Following interface{} `json:"following"` + ShowAllInlineMedia bool `json:"show_all_inline_media"` + ScreenName string `json:"screen_name"` +} + +type Statuses struct { + Coordinates interface{} `json:"coordinates"` + Favorited bool `json:"favorited"` + Truncated bool `json:"truncated"` + CreatedAt string `json:"created_at"` + IDStr string `json:"id_str"` + Entities Entities `json:"entities"` + InReplyToUserIDStr interface{} `json:"in_reply_to_user_id_str"` + Contributors interface{} `json:"contributors"` + Text string `json:"text"` + Metadata Metadata `json:"metadata"` + RetweetCount int `json:"retweet_count"` + InReplyToStatusIDStr interface{} `json:"in_reply_to_status_id_str"` + ID int64 `json:"id"` + Geo interface{} `json:"geo"` + Retweeted bool `json:"retweeted"` + InReplyToUserID interface{} `json:"in_reply_to_user_id"` + Place interface{} `json:"place"` + User User `json:"user"` + InReplyToScreenName interface{} `json:"in_reply_to_screen_name"` + Source string `json:"source"` + InReplyToStatusID interface{} `json:"in_reply_to_status_id"` +} + +type SearchMetadata struct { + MaxID int64 `json:"max_id"` + SinceID int64 `json:"since_id"` + RefreshURL string `json:"refresh_url"` + NextResults string `json:"next_results"` + Count int `json:"count"` + CompletedIn float64 `json:"completed_in"` + SinceIDStr string `json:"since_id_str"` + Query string `json:"query"` + MaxIDStr string `json:"max_id_str"` +}