├── .codecov.yml ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml ├── dependency-review-config.yml ├── release.yml ├── stale.yml └── workflows │ ├── pr.yml │ └── x.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── Makefile ├── README.md ├── _bench ├── README.md ├── bench_test.go ├── doc.go ├── generate.go ├── go.mod ├── go.sum ├── hello_world.go ├── hello_world_easyjson.go ├── hello_world_ffjson.go ├── hello_world_ffjson_gen.go ├── hello_world_test.go ├── small.go ├── small_easyjson.go ├── small_test.go ├── sonic_skip_test.go └── sonic_test.go ├── alloc_test.go ├── any_test.go ├── bench_test.go ├── bool_test.go ├── crawl_test.go ├── dec.go ├── dec_arr.go ├── dec_arr_iter.go ├── dec_arr_iter_test.go ├── dec_arr_test.go ├── dec_b64.go ├── dec_b64_test.go ├── dec_bool.go ├── dec_bool_test.go ├── dec_capture.go ├── dec_capture_test.go ├── dec_depth.go ├── dec_error.go ├── dec_error_test.go ├── dec_float.go ├── dec_float_big.go ├── dec_float_test.go ├── dec_int.gen.go ├── dec_int.go ├── dec_int_fuzz_test.go ├── dec_int_test.go ├── dec_null.go ├── dec_null_test.go ├── dec_num.go ├── dec_num_test.go ├── dec_obj.go ├── dec_obj_iter.go ├── dec_obj_iter_test.go ├── dec_obj_test.go ├── dec_raw.go ├── dec_raw_test.go ├── dec_read.go ├── dec_read_test.go ├── dec_skip.go ├── dec_skip_bench_test.go ├── dec_skip_cases_test.go ├── dec_skip_test.go ├── dec_str.go ├── dec_str_test.go ├── dec_test.go ├── dec_validate.go ├── dec_validate_test.go ├── enc.go ├── enc_b64.go ├── enc_b64_test.go ├── enc_bench_test.go ├── enc_comma.go ├── enc_comma_test.go ├── enc_float.go ├── enc_int.go ├── enc_int_test.go ├── enc_num.go ├── enc_str.go ├── enc_str_escape.go ├── enc_str_test.go ├── enc_stream.go ├── enc_stream_test.go ├── enc_test.go ├── examples_test.go ├── float_bench_test.go ├── float_test.go ├── fuzz_test.go ├── generate.go ├── go.coverage.sh ├── go.mod ├── go.sum ├── go.test.sh ├── int_bench_test.go ├── int_test.go ├── internal └── byteseq │ ├── byteseq.go │ └── byteseq_test.go ├── invalid_test.go ├── jx.go ├── jx_test.go ├── null_test.go ├── num.go ├── num_test.go ├── obj_test.go ├── otel_test.go ├── pipe_test.go ├── suite_test.go ├── testdata ├── bools.json ├── canada.json ├── citm_catalog.json ├── floats.json ├── fuzz │ └── FuzzDecEncReader │ │ └── 609325384ef131657676fe927dc57052657bb3b262b93b3b655db562979df9df ├── integers.json ├── large.json ├── medium.json ├── nulls.json ├── otel_ex_1.json ├── slow_floats.json ├── small.json ├── test_parsing │ ├── LICENSE │ ├── i_number_double_huge_neg_exp.json │ ├── i_number_huge_exp.json │ ├── i_number_neg_int_huge_exp.json │ ├── i_number_pos_double_huge_exp.json │ ├── i_number_real_neg_overflow.json │ ├── i_number_real_pos_overflow.json │ ├── i_number_real_underflow.json │ ├── i_number_too_big_neg_int.json │ ├── i_number_too_big_pos_int.json │ ├── i_number_very_big_negative_int.json │ ├── i_object_key_lone_2nd_surrogate.json │ ├── i_string_1st_surrogate_but_2nd_missing.json │ ├── i_string_1st_valid_surrogate_2nd_invalid.json │ ├── i_string_UTF-16LE_with_BOM.json │ ├── i_string_UTF-8_invalid_sequence.json │ ├── i_string_UTF8_surrogate_U+D800.json │ ├── i_string_incomplete_surrogate_and_escape_valid.json │ ├── i_string_incomplete_surrogate_pair.json │ ├── i_string_incomplete_surrogates_escape_valid.json │ ├── i_string_invalid_lonely_surrogate.json │ ├── i_string_invalid_surrogate.json │ ├── i_string_invalid_utf-8.json │ ├── i_string_inverted_surrogates_U+1D11E.json │ ├── i_string_iso_latin_1.json │ ├── i_string_lone_second_surrogate.json │ ├── i_string_lone_utf8_continuation_byte.json │ ├── i_string_not_in_unicode_range.json │ ├── i_string_overlong_sequence_2_bytes.json │ ├── i_string_overlong_sequence_6_bytes.json │ ├── i_string_overlong_sequence_6_bytes_null.json │ ├── i_string_truncated-utf-8.json │ ├── i_string_utf16BE_no_BOM.json │ ├── i_string_utf16LE_no_BOM.json │ ├── i_structure_500_nested_arrays.json │ ├── i_structure_UTF-8_BOM_empty_object.json │ ├── n_array_1_true_without_comma.json │ ├── n_array_a_invalid_utf8.json │ ├── n_array_colon_instead_of_comma.json │ ├── n_array_comma_after_close.json │ ├── n_array_comma_and_number.json │ ├── n_array_double_comma.json │ ├── n_array_double_extra_comma.json │ ├── n_array_extra_close.json │ ├── n_array_extra_comma.json │ ├── n_array_incomplete.json │ ├── n_array_incomplete_invalid_value.json │ ├── n_array_inner_array_no_comma.json │ ├── n_array_invalid_utf8.json │ ├── n_array_items_separated_by_semicolon.json │ ├── n_array_just_comma.json │ ├── n_array_just_minus.json │ ├── n_array_missing_value.json │ ├── n_array_newlines_unclosed.json │ ├── n_array_number_and_comma.json │ ├── n_array_number_and_several_commas.json │ ├── n_array_spaces_vertical_tab_formfeed.json │ ├── n_array_star_inside.json │ ├── n_array_unclosed.json │ ├── n_array_unclosed_trailing_comma.json │ ├── n_array_unclosed_with_new_lines.json │ ├── n_array_unclosed_with_object_inside.json │ ├── n_incomplete_false.json │ ├── n_incomplete_null.json │ ├── n_incomplete_true.json │ ├── n_multidigit_number_then_00.json │ ├── n_number_++.json │ ├── n_number_+1.json │ ├── n_number_+Inf.json │ ├── n_number_-01.json │ ├── n_number_-1.0..json │ ├── n_number_-2..json │ ├── n_number_-NaN.json │ ├── n_number_.-1.json │ ├── n_number_.2e-3.json │ ├── n_number_0.1.2.json │ ├── n_number_0.3e+.json │ ├── n_number_0.3e.json │ ├── n_number_0.e1.json │ ├── n_number_0_capital_E+.json │ ├── n_number_0_capital_E.json │ ├── n_number_0e+.json │ ├── n_number_0e.json │ ├── n_number_1.0e+.json │ ├── n_number_1.0e-.json │ ├── n_number_1.0e.json │ ├── n_number_1_000.json │ ├── n_number_1eE2.json │ ├── n_number_2.e+3.json │ ├── n_number_2.e-3.json │ ├── n_number_2.e3.json │ ├── n_number_9.e+.json │ ├── n_number_Inf.json │ ├── n_number_NaN.json │ ├── n_number_U+FF11_fullwidth_digit_one.json │ ├── n_number_expression.json │ ├── n_number_hex_1_digit.json │ ├── n_number_hex_2_digits.json │ ├── n_number_infinity.json │ ├── n_number_invalid+-.json │ ├── n_number_invalid-negative-real.json │ ├── n_number_invalid-utf-8-in-bigger-int.json │ ├── n_number_invalid-utf-8-in-exponent.json │ ├── n_number_invalid-utf-8-in-int.json │ ├── n_number_minus_infinity.json │ ├── n_number_minus_sign_with_trailing_garbage.json │ ├── n_number_minus_space_1.json │ ├── n_number_neg_int_starting_with_zero.json │ ├── n_number_neg_real_without_int_part.json │ ├── n_number_neg_with_garbage_at_end.json │ ├── n_number_real_garbage_after_e.json │ ├── n_number_real_with_invalid_utf8_after_e.json │ ├── n_number_real_without_fractional_part.json │ ├── n_number_starting_with_dot.json │ ├── n_number_with_alpha.json │ ├── n_number_with_alpha_char.json │ ├── n_number_with_leading_zero.json │ ├── n_object_bad_value.json │ ├── n_object_bracket_key.json │ ├── n_object_comma_instead_of_colon.json │ ├── n_object_double_colon.json │ ├── n_object_emoji.json │ ├── n_object_garbage_at_end.json │ ├── n_object_key_with_single_quotes.json │ ├── n_object_lone_continuation_byte_in_key_and_trailing_comma.json │ ├── n_object_missing_colon.json │ ├── n_object_missing_key.json │ ├── n_object_missing_semicolon.json │ ├── n_object_missing_value.json │ ├── n_object_no-colon.json │ ├── n_object_non_string_key.json │ ├── n_object_non_string_key_but_huge_number_instead.json │ ├── n_object_repeated_null_null.json │ ├── n_object_several_trailing_commas.json │ ├── n_object_single_quote.json │ ├── n_object_trailing_comma.json │ ├── n_object_trailing_comment.json │ ├── n_object_trailing_comment_open.json │ ├── n_object_trailing_comment_slash_open.json │ ├── n_object_trailing_comment_slash_open_incomplete.json │ ├── n_object_two_commas_in_a_row.json │ ├── n_object_unquoted_key.json │ ├── n_object_unterminated-value.json │ ├── n_object_with_single_string.json │ ├── n_object_with_trailing_garbage.json │ ├── n_single_space.json │ ├── n_string_1_surrogate_then_escape.json │ ├── n_string_1_surrogate_then_escape_u.json │ ├── n_string_1_surrogate_then_escape_u1.json │ ├── n_string_1_surrogate_then_escape_u1x.json │ ├── n_string_accentuated_char_no_quotes.json │ ├── n_string_backslash_00.json │ ├── n_string_escape_x.json │ ├── n_string_escaped_backslash_bad.json │ ├── n_string_escaped_ctrl_char_tab.json │ ├── n_string_escaped_emoji.json │ ├── n_string_incomplete_escape.json │ ├── n_string_incomplete_escaped_character.json │ ├── n_string_incomplete_surrogate.json │ ├── n_string_incomplete_surrogate_escape_invalid.json │ ├── n_string_invalid-utf-8-in-escape.json │ ├── n_string_invalid_backslash_esc.json │ ├── n_string_invalid_unicode_escape.json │ ├── n_string_invalid_utf8_after_escape.json │ ├── n_string_leading_uescaped_thinspace.json │ ├── n_string_no_quotes_with_bad_escape.json │ ├── n_string_single_doublequote.json │ ├── n_string_single_quote.json │ ├── n_string_single_string_no_double_quotes.json │ ├── n_string_start_escape_unclosed.json │ ├── n_string_unescaped_ctrl_char.json │ ├── n_string_unescaped_newline.json │ ├── n_string_unescaped_tab.json │ ├── n_string_unicode_CapitalU.json │ ├── n_string_with_trailing_garbage.json │ ├── n_structure_100000_opening_arrays.json │ ├── n_structure_U+2060_word_joined.json │ ├── n_structure_UTF8_BOM_no_data.json │ ├── n_structure_angle_bracket_..json │ ├── n_structure_angle_bracket_null.json │ ├── n_structure_array_trailing_garbage.json │ ├── n_structure_array_with_extra_array_close.json │ ├── n_structure_array_with_unclosed_string.json │ ├── n_structure_ascii-unicode-identifier.json │ ├── n_structure_capitalized_True.json │ ├── n_structure_close_unopened_array.json │ ├── n_structure_comma_instead_of_closing_brace.json │ ├── n_structure_double_array.json │ ├── n_structure_end_array.json │ ├── n_structure_incomplete_UTF8_BOM.json │ ├── n_structure_lone-invalid-utf-8.json │ ├── n_structure_lone-open-bracket.json │ ├── n_structure_no_data.json │ ├── n_structure_null-byte-outside-string.json │ ├── n_structure_number_with_trailing_garbage.json │ ├── n_structure_object_followed_by_closing_object.json │ ├── n_structure_object_unclosed_no_value.json │ ├── n_structure_object_with_comment.json │ ├── n_structure_object_with_trailing_garbage.json │ ├── n_structure_open_array_apostrophe.json │ ├── n_structure_open_array_comma.json │ ├── n_structure_open_array_object.json │ ├── n_structure_open_array_open_object.json │ ├── n_structure_open_array_open_string.json │ ├── n_structure_open_array_string.json │ ├── n_structure_open_object.json │ ├── n_structure_open_object_close_array.json │ ├── n_structure_open_object_comma.json │ ├── n_structure_open_object_open_array.json │ ├── n_structure_open_object_open_string.json │ ├── n_structure_open_object_string_with_apostrophes.json │ ├── n_structure_open_open.json │ ├── n_structure_single_eacute.json │ ├── n_structure_single_star.json │ ├── n_structure_trailing_#.json │ ├── n_structure_uescaped_LF_before_string.json │ ├── n_structure_unclosed_array.json │ ├── n_structure_unclosed_array_partial_null.json │ ├── n_structure_unclosed_array_unfinished_false.json │ ├── n_structure_unclosed_array_unfinished_true.json │ ├── n_structure_unclosed_object.json │ ├── n_structure_unicode-identifier.json │ ├── n_structure_whitespace_U+2060_word_joiner.json │ ├── n_structure_whitespace_formfeed.json │ ├── y_array_arraysWithSpaces.json │ ├── y_array_empty-string.json │ ├── y_array_empty.json │ ├── y_array_ending_with_newline.json │ ├── y_array_false.json │ ├── y_array_heterogeneous.json │ ├── y_array_null.json │ ├── y_array_with_1_and_newline.json │ ├── y_array_with_leading_space.json │ ├── y_array_with_several_null.json │ ├── y_array_with_trailing_space.json │ ├── y_number.json │ ├── y_number_0e+1.json │ ├── y_number_0e1.json │ ├── y_number_after_space.json │ ├── y_number_double_close_to_zero.json │ ├── y_number_int_with_exp.json │ ├── y_number_minus_zero.json │ ├── y_number_negative_int.json │ ├── y_number_negative_one.json │ ├── y_number_negative_zero.json │ ├── y_number_real_capital_e.json │ ├── y_number_real_capital_e_neg_exp.json │ ├── y_number_real_capital_e_pos_exp.json │ ├── y_number_real_exponent.json │ ├── y_number_real_fraction_exponent.json │ ├── y_number_real_neg_exp.json │ ├── y_number_real_pos_exponent.json │ ├── y_number_simple_int.json │ ├── y_number_simple_real.json │ ├── y_object.json │ ├── y_object_basic.json │ ├── y_object_duplicated_key.json │ ├── y_object_duplicated_key_and_value.json │ ├── y_object_empty.json │ ├── y_object_empty_key.json │ ├── y_object_escaped_null_in_key.json │ ├── y_object_extreme_numbers.json │ ├── y_object_long_strings.json │ ├── y_object_simple.json │ ├── y_object_string_unicode.json │ ├── y_object_with_newlines.json │ ├── y_string_1_2_3_bytes_UTF-8_sequences.json │ ├── y_string_accepted_surrogate_pair.json │ ├── y_string_accepted_surrogate_pairs.json │ ├── y_string_allowed_escapes.json │ ├── y_string_backslash_and_u_escaped_zero.json │ ├── y_string_backslash_doublequotes.json │ ├── y_string_comments.json │ ├── y_string_double_escape_a.json │ ├── y_string_double_escape_n.json │ ├── y_string_escaped_control_character.json │ ├── y_string_escaped_noncharacter.json │ ├── y_string_in_array.json │ ├── y_string_in_array_with_leading_space.json │ ├── y_string_last_surrogates_1_and_2.json │ ├── y_string_nbsp_uescaped.json │ ├── y_string_nonCharacterInUTF-8_U+10FFFF.json │ ├── y_string_nonCharacterInUTF-8_U+FFFF.json │ ├── y_string_null_escape.json │ ├── y_string_one-byte-utf-8.json │ ├── y_string_pi.json │ ├── y_string_reservedCharacterInUTF-8_U+1BFFF.json │ ├── y_string_simple_ascii.json │ ├── y_string_space.json │ ├── y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json │ ├── y_string_three-byte-utf-8.json │ ├── y_string_two-byte-utf-8.json │ ├── y_string_u+2028_line_sep.json │ ├── y_string_u+2029_par_sep.json │ ├── y_string_uEscape.json │ ├── y_string_uescaped_newline.json │ ├── y_string_unescaped_char_delete.json │ ├── y_string_unicode.json │ ├── y_string_unicodeEscapedBackslash.json │ ├── y_string_unicode_2.json │ ├── y_string_unicode_U+10FFFE_nonchar.json │ ├── y_string_unicode_U+1FFFE_nonchar.json │ ├── y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json │ ├── y_string_unicode_U+2064_invisible_plus.json │ ├── y_string_unicode_U+FDD0_nonchar.json │ ├── y_string_unicode_U+FFFE_nonchar.json │ ├── y_string_unicode_escaped_double_quote.json │ ├── y_string_utf8.json │ ├── y_string_with_del_character.json │ ├── y_structure_lonely_false.json │ ├── y_structure_lonely_int.json │ ├── y_structure_lonely_negative_real.json │ ├── y_structure_lonely_null.json │ ├── y_structure_lonely_string.json │ ├── y_structure_lonely_true.json │ ├── y_structure_string_empty.json │ ├── y_structure_trailing_newline.json │ ├── y_structure_true_in_array.json │ └── y_structure_whitespace_array.json ├── tiny.json └── twitter.json ├── tools └── mkint │ ├── decode.tmpl │ ├── encode.tmpl │ └── main.go ├── w.go ├── w_b64.go ├── w_b64_test.go ├── w_float.go ├── w_float_bits.go ├── w_int.gen.go ├── w_int.go ├── w_num.go ├── w_str.go ├── w_str_escape.go ├── w_stream.go └── w_test.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - tools/** 3 | coverage: 4 | status: 5 | patch: false 6 | project: 7 | default: 8 | threshold: 0.5% 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | 11 | [{*.go, go.mod, *.tmpl}] 12 | indent_style = tab 13 | indent_size = 4 14 | 15 | [{*.yml,*.yaml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.py] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | # Makefiles always use tabs for indentation 24 | [Makefile] 25 | indent_style = tab 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Never modify line endings of corpus. 2 | testdata/fuzz/** text eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: github-actions 8 | directory: "/" 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/dependency-review-config.yml: -------------------------------------------------------------------------------- 1 | fail_on_severity: "low" 2 | allow_licenses: 3 | - "MIT" 4 | - "ISC" 5 | - "MPL-2.0" 6 | - "BSD-2-Clause" 7 | - "BSD-3-Clause" 8 | - "Apache-2.0" 9 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 366 3 | 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: 30 6 | 7 | # Issues with these labels will never be considered stale 8 | exemptLabels: 9 | - pinned 10 | - security 11 | - bug 12 | - blocked 13 | - protected 14 | - triaged 15 | 16 | # Label to use when marking an issue as stale 17 | staleLabel: stale 18 | 19 | # Comment to post when marking an issue as stale. Set to `false` to disable 20 | markComment: > 21 | This issue has been automatically marked as stale because it has not had 22 | recent activity. It will be closed if no further activity occurs. Thank you 23 | for your contributions. 24 | 25 | # Comment to post when closing a stale issue. Set to `false` to disable 26 | closeComment: false -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: "Dependency Review" 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | dependency-review: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "Checkout Repository" 12 | uses: actions/checkout@v4 13 | 14 | # Their stupid action rely on PR event metadata, so 15 | # there is no much sense to setup a re-usable workflow. 16 | - name: "Dependency Review" 17 | uses: actions/dependency-review-action@v4 18 | with: 19 | config-file: "./.github/dependency-review-config.yml" 20 | -------------------------------------------------------------------------------- /.github/workflows/x.yml: -------------------------------------------------------------------------------- 1 | name: x 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | # Common Go workflows from go faster 10 | # See https://github.com/go-faster/x 11 | jobs: 12 | test: 13 | uses: go-faster/x/.github/workflows/test.yml@main 14 | cover: 15 | uses: go-faster/x/.github/workflows/cover.yml@main 16 | lint: 17 | uses: go-faster/x/.github/workflows/lint.yml@main 18 | commit: 19 | uses: go-faster/x/.github/workflows/commit.yml@main 20 | codeql: 21 | uses: go-faster/x/.github/workflows/codeql.yml@main 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /bug_test.go 3 | /coverage.txt 4 | /.idea 5 | .idea 6 | _bin/* 7 | ./examples 8 | 9 | *-fuzz.zip 10 | 11 | *.out 12 | *.dump 13 | *.test 14 | 15 | corpus 16 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | gocyclo: 3 | min-complexity: 15 4 | maligned: 5 | suggest-new: true 6 | dupl: 7 | threshold: 120 8 | goconst: 9 | min-len: 2 10 | min-occurrences: 3 11 | misspell: 12 | locale: US 13 | lll: 14 | line-length: 140 15 | goimports: 16 | local-prefixes: github.com/go-faster/ 17 | revive: 18 | rules: 19 | - disabled: true 20 | name: unused-parameter 21 | gocritic: 22 | enabled-tags: 23 | - diagnostic 24 | - experimental 25 | - opinionated 26 | - performance 27 | - style 28 | disabled-checks: 29 | - hugeParam 30 | - rangeValCopy 31 | - exitAfterDefer 32 | - whyNoLint 33 | - singleCaseSwitch 34 | - commentedOutCode 35 | - appendAssign 36 | - unnecessaryBlock 37 | - redundantSprint 38 | 39 | linters: 40 | disable-all: true 41 | enable: 42 | - dogsled 43 | - errcheck 44 | - goconst 45 | - gocritic 46 | - gofmt 47 | - goimports 48 | - revive 49 | - gosec 50 | - govet 51 | - ineffassign 52 | - misspell 53 | - nakedret 54 | - typecheck 55 | - unconvert 56 | - whitespace 57 | 58 | # Breaks with buildssa error for some reason. 59 | #- unparam 60 | 61 | # Do not enable: 62 | # - wsl (too opinionated about newlines) 63 | # - godox (todos are OK) 64 | # - bodyclose (false positives on helper functions) 65 | # - prealloc (not worth it in scope of this project) 66 | # - maligned (same as prealloc) 67 | # - funlen (gocyclo is enough) 68 | # - gochecknoglobals (we know when it is ok to use globals) 69 | # - gochecknoinits (we know when it is ok to use inits) 70 | # - dupl (too opinionated) 71 | 72 | issues: 73 | exclude-use-default: false 74 | exclude-rules: 75 | # Disable linters that are annoying in tests. 76 | - path: _test\.go 77 | linters: 78 | - gocyclo 79 | - errcheck 80 | - dupl 81 | - gosec 82 | - funlen 83 | - goconst 84 | - gocognit 85 | - scopelint 86 | - lll 87 | 88 | - path: _test\.go 89 | text: "Combine" 90 | linters: 91 | - gocritic 92 | 93 | # Check that equal to self is true 94 | - linters: [gocritic] 95 | source: "(assert|require).+Equal" 96 | text: "dupArg" 97 | path: _test\.go 98 | 99 | # Ignore shadowing of err. 100 | - linters: [govet] 101 | text: 'declaration of "(err|ctx|log|c)"' 102 | 103 | # Ignore linters in main packages. 104 | - path: main\.go 105 | linters: [goconst, funlen, gocognit, gocyclo] 106 | 107 | # Too much false-positives. 108 | - linters: [gosec] 109 | text: G115 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 json-iterator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @./go.test.sh 3 | .PHONY: test 4 | 5 | coverage: 6 | @./go.coverage.sh 7 | .PHONY: coverage 8 | 9 | test_fast: 10 | go test ./... 11 | 12 | tidy: 13 | go mod tidy 14 | -------------------------------------------------------------------------------- /_bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | * jx 4 | * jsoniter (should same as jx in most cases) 5 | * encoding/json 6 | * bytedance/sonic 7 | * mailru/easyjson 8 | * pquerna/ffjson 9 | 10 | | name | ns/op | MB/s | B/op | allocs/op | 11 | |---------------------------------|-------|---------|------|-----------| 12 | | HelloWorld/Encode/Baseline | 3.75 | 7466.65 | 0 | 0 | 13 | | HelloWorld/Encode/easyjson | 21.46 | 1304.66 | 0 | 0 | 14 | | HelloWorld/Encode/ffjson | 105.8 | 264.53 | 16 | 1 | 15 | | HelloWorld/Encode/json-iterator | 35.11 | 797.52 | 0 | 0 | 16 | | HelloWorld/Encode/jx/Encoder | 30.49 | 918.33 | 0 | 0 | 17 | | HelloWorld/Encode/jx/Writer | 14.27 | 1962.1 | 0 | 0 | 18 | | HelloWorld/Encode/sonic | 105.5 | 265.44 | 21 | 1 | 19 | | HelloWorld/Encode/std | 85.99 | 325.63 | 0 | 0 | 20 | | HelloWorld/Scan/jscan | 43.47 | 644.09 | 0 | 0 | 21 | | HelloWorld/Scan/jx | 45.93 | 609.63 | 0 | 0 | 22 | | Small/Decode/easyjson | 1381 | 245.46 | 32 | 7 | 23 | | Small/Decode/sonic | 743 | 456.27 | 1 | 0 | 24 | | Small/Decode/std | 7836 | 43.26 | 400 | 24 | 25 | | Small/Encode/easyjson | 455.2 | 744.76 | 0 | 0 | 26 | | Small/Encode/jx/Encoder | 626.9 | 540.75 | 0 | 0 | 27 | | Small/Encode/jx/Writer | 310.4 | 1092.08 | 0 | 0 | 28 | | Small/Encode/sonic | 454.6 | 745.74 | 18 | 1 | 29 | | Small/Encode/std | 871.4 | 389.03 | 0 | 0 | 30 | | Small/Scan/jscan | 631.2 | 537.04 | 0 | 0 | 31 | | Small/Scan/jx | 849 | 399.29 | 0 | 0 | 32 | -------------------------------------------------------------------------------- /_bench/bench_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | // Common names for benchmarks. 4 | const ( 5 | // Encode is name for encoding benchmarks. 6 | Encode = "Encode" 7 | // Decode is name for decoding benchmarks. 8 | Decode = "Decode" 9 | // Scan is name for scanning benchmarks. 10 | Scan = "Scan" 11 | // JX is name for benchmarks related to go-faster/jx package. 12 | JX = "jx" 13 | // Std is name for benchmarks related to encoding/json. 14 | Std = "std" 15 | // Sonnet for sugawarayuuta/sonnet. 16 | Sonnet = "sonnet" 17 | // Sonic is name for benchmarks related to bytedance/sonic package. 18 | Sonic = "sonic" 19 | // JSONIter for json-iterator/go. 20 | JSONIter = "json-iterator" 21 | // EasyJSON for mailru/easyjson. 22 | EasyJSON = "easyjson" 23 | // FFJSON for pquerna/ffjson. 24 | FFJSON = "ffjson" 25 | // JScan for romshark/jscan. 26 | JScan = "jscan" 27 | // Baseline directly writes string to buffer, no encoding. 28 | Baseline = "Baseline" 29 | ) 30 | -------------------------------------------------------------------------------- /_bench/doc.go: -------------------------------------------------------------------------------- 1 | // Package bench implements benchmarks for jx package and should not be 2 | // used directly. 3 | package bench 4 | -------------------------------------------------------------------------------- /_bench/generate.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | //go:generate go run github.com/mailru/easyjson/easyjson -no_std_marshalers hello_world.go 4 | //go:generate go run github.com/mailru/easyjson/easyjson -no_std_marshalers small.go 5 | //go:generate go run github.com/pquerna/ffjson -w hello_world_ffjson_gen.go hello_world_ffjson.go 6 | -------------------------------------------------------------------------------- /_bench/go.mod: -------------------------------------------------------------------------------- 1 | module bench 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.8.8 7 | github.com/go-faster/jx v0.0.0-replaced 8 | github.com/json-iterator/go v1.1.12 9 | github.com/mailru/easyjson v0.7.7 10 | github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 11 | github.com/romshark/jscan v1.0.0 12 | github.com/sugawarayuuta/sonnet v0.0.0-20230425054915-e28ba49e3d17 13 | ) 14 | 15 | require ( 16 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 17 | github.com/go-faster/errors v0.6.1 // indirect 18 | github.com/josharian/intern v1.0.0 // indirect 19 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 21 | github.com/modern-go/reflect2 v1.0.2 // indirect 22 | github.com/segmentio/asm v1.2.0 // indirect 23 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 24 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 25 | golang.org/x/sys v0.1.0 // indirect 26 | ) 27 | 28 | // replace to current repository version 29 | replace github.com/go-faster/jx => ../ 30 | 31 | // CVE-2022-28948 32 | replace gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.0 33 | -------------------------------------------------------------------------------- /_bench/hello_world.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | jsoniter "github.com/json-iterator/go" 5 | 6 | "github.com/go-faster/jx" 7 | ) 8 | 9 | // HelloWorld case. 10 | // 11 | // Example: 12 | // {"message": "Hello, world!"} 13 | 14 | const ( 15 | helloWorldField = "message" 16 | helloWorldMessage = "Hello, world!" 17 | helloWorld = `{"message": "Hello, world!"}` 18 | ) 19 | 20 | //easyjson:json 21 | type HelloWorld struct { 22 | Message string `json:"message"` 23 | } 24 | 25 | func (w HelloWorld) Encode(e *jx.Encoder) { 26 | e.ObjStart() 27 | e.FieldStart(helloWorldField) 28 | e.Str(w.Message) 29 | e.ObjEnd() 30 | } 31 | 32 | func (w HelloWorld) Write(wr *jx.Writer) { 33 | wr.ObjStart() 34 | wr.RawStr(`"message":`) 35 | wr.Str(w.Message) 36 | wr.ObjEnd() 37 | } 38 | 39 | func (w HelloWorld) EncodeIter(s *jsoniter.Stream) { 40 | s.WriteObjectStart() 41 | s.WriteObjectField(helloWorldField) 42 | s.WriteString(w.Message) 43 | s.WriteObjectEnd() 44 | } 45 | -------------------------------------------------------------------------------- /_bench/hello_world_easyjson.go: -------------------------------------------------------------------------------- 1 | // Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. 2 | 3 | package bench 4 | 5 | import ( 6 | json "encoding/json" 7 | 8 | easyjson "github.com/mailru/easyjson" 9 | jlexer "github.com/mailru/easyjson/jlexer" 10 | jwriter "github.com/mailru/easyjson/jwriter" 11 | ) 12 | 13 | // suppress unused package warning 14 | var ( 15 | _ *json.RawMessage 16 | _ *jlexer.Lexer 17 | _ *jwriter.Writer 18 | _ easyjson.Marshaler 19 | ) 20 | 21 | func easyjson80ca00c3DecodeBench(in *jlexer.Lexer, out *HelloWorld) { 22 | isTopLevel := in.IsStart() 23 | if in.IsNull() { 24 | if isTopLevel { 25 | in.Consumed() 26 | } 27 | in.Skip() 28 | return 29 | } 30 | in.Delim('{') 31 | for !in.IsDelim('}') { 32 | key := in.UnsafeFieldName(false) 33 | in.WantColon() 34 | if in.IsNull() { 35 | in.Skip() 36 | in.WantComma() 37 | continue 38 | } 39 | switch key { 40 | case "message": 41 | out.Message = string(in.String()) 42 | default: 43 | in.SkipRecursive() 44 | } 45 | in.WantComma() 46 | } 47 | in.Delim('}') 48 | if isTopLevel { 49 | in.Consumed() 50 | } 51 | } 52 | func easyjson80ca00c3EncodeBench(out *jwriter.Writer, in HelloWorld) { 53 | out.RawByte('{') 54 | first := true 55 | _ = first 56 | { 57 | const prefix string = ",\"message\":" 58 | out.RawString(prefix[1:]) 59 | out.String(string(in.Message)) 60 | } 61 | out.RawByte('}') 62 | } 63 | 64 | // MarshalEasyJSON supports easyjson.Marshaler interface 65 | func (v HelloWorld) MarshalEasyJSON(w *jwriter.Writer) { 66 | easyjson80ca00c3EncodeBench(w, v) 67 | } 68 | 69 | // UnmarshalEasyJSON supports easyjson.Unmarshaler interface 70 | func (v *HelloWorld) UnmarshalEasyJSON(l *jlexer.Lexer) { 71 | easyjson80ca00c3DecodeBench(l, v) 72 | } 73 | -------------------------------------------------------------------------------- /_bench/hello_world_ffjson.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | type HelloWorldFFJSON struct { 4 | Message string `json:"message"` 5 | } 6 | -------------------------------------------------------------------------------- /_bench/hello_world_test.go: -------------------------------------------------------------------------------- 1 | package bench 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | jsoniter "github.com/json-iterator/go" 9 | "github.com/mailru/easyjson/jwriter" 10 | fflib "github.com/pquerna/ffjson/fflib/v1" 11 | "github.com/romshark/jscan" 12 | "github.com/sugawarayuuta/sonnet" 13 | 14 | "github.com/go-faster/jx" 15 | ) 16 | 17 | // setupHelloWorld should be called on each "HelloWorld" benchmark. 18 | func setupHelloWorld(b *testing.B) { 19 | b.Helper() 20 | b.ReportAllocs() 21 | b.SetBytes(int64(len(helloWorld))) 22 | } 23 | 24 | func BenchmarkHelloWorld(b *testing.B) { 25 | v := &HelloWorld{Message: helloWorldMessage} 26 | b.Run(Encode, func(b *testing.B) { 27 | b.Run(JX, func(b *testing.B) { 28 | b.Run("Encoder", func(b *testing.B) { 29 | setupHelloWorld(b) 30 | var e jx.Encoder 31 | for i := 0; i < b.N; i++ { 32 | e.Reset() 33 | v.Encode(&e) 34 | } 35 | }) 36 | b.Run("Writer", func(b *testing.B) { 37 | setupHelloWorld(b) 38 | var w jx.Writer 39 | for i := 0; i < b.N; i++ { 40 | w.Reset() 41 | v.Write(&w) 42 | } 43 | }) 44 | }) 45 | b.Run(Std, func(b *testing.B) { 46 | w := new(bytes.Buffer) 47 | e := json.NewEncoder(w) 48 | setupHelloWorld(b) 49 | for i := 0; i < b.N; i++ { 50 | w.Reset() 51 | if err := e.Encode(v); err != nil { 52 | b.Fatal(err) 53 | } 54 | } 55 | }) 56 | b.Run(Sonnet, func(b *testing.B) { 57 | w := new(bytes.Buffer) 58 | e := sonnet.NewEncoder(w) 59 | setupHelloWorld(b) 60 | for i := 0; i < b.N; i++ { 61 | w.Reset() 62 | if err := e.Encode(v); err != nil { 63 | b.Fatal(err) 64 | } 65 | } 66 | }) 67 | b.Run(Sonic, sonicHelloWorld) 68 | b.Run(JSONIter, func(b *testing.B) { 69 | s := jsoniter.NewStream(jsoniter.ConfigFastest, nil, 1024) 70 | setupHelloWorld(b) 71 | for i := 0; i < b.N; i++ { 72 | s.SetBuffer(s.Buffer()[:0]) // reset buffer 73 | v.EncodeIter(s) 74 | } 75 | }) 76 | b.Run(EasyJSON, func(b *testing.B) { 77 | jw := jwriter.Writer{} 78 | setupHelloWorld(b) 79 | for i := 0; i < b.N; i++ { 80 | jw.Buffer.Buf = jw.Buffer.Buf[:0] // reset 81 | v.MarshalEasyJSON(&jw) 82 | } 83 | }) 84 | b.Run(FFJSON, func(b *testing.B) { 85 | var buf fflib.EncodingBuffer = new(fflib.Buffer) 86 | v := &HelloWorldFFJSON{Message: helloWorldMessage} 87 | setupHelloWorld(b) 88 | for i := 0; i < b.N; i++ { 89 | buf.Reset() 90 | if err := v.MarshalJSONBuf(buf); err != nil { 91 | b.Fatal(err) 92 | } 93 | } 94 | }) 95 | b.Run(Baseline, func(b *testing.B) { 96 | setupHelloWorld(b) 97 | buf := new(bytes.Buffer) 98 | for i := 0; i < b.N; i++ { 99 | buf.Reset() 100 | buf.WriteString(helloWorld) 101 | } 102 | }) 103 | }) 104 | b.Run(Scan, func(b *testing.B) { 105 | b.Run(JX, func(b *testing.B) { 106 | setupHelloWorld(b) 107 | var d jx.Decoder 108 | data := []byte(helloWorld) 109 | for i := 0; i < b.N; i++ { 110 | d.ResetBytes(data) 111 | if err := d.Skip(); err != nil { 112 | b.Fatal() 113 | } 114 | } 115 | }) 116 | b.Run(JScan, func(b *testing.B) { 117 | setupHelloWorld(b) 118 | for i := 0; i < b.N; i++ { 119 | r := jscan.Scan( 120 | jscan.Options{}, 121 | helloWorld, 122 | func(i *jscan.Iterator) bool { return false }, 123 | ) 124 | if r.IsErr() { 125 | b.Fatal("err") 126 | } 127 | } 128 | }) 129 | }) 130 | } 131 | -------------------------------------------------------------------------------- /_bench/sonic_skip_test.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | 3 | package bench 4 | 5 | import "testing" 6 | 7 | func sonicSkip(b *testing.B) { 8 | // Sonic tests are skipped because sonic does not support go1.18. 9 | // Ref: 10 | // https://github.com/bytedance/sonic/pull/116 11 | // https://github.com/bytedance/sonic/issues/75 12 | b.Helper() 13 | b.Skip("not supported on current go version") 14 | } 15 | 16 | func sonicHelloWorld(b *testing.B) { 17 | b.Helper() 18 | sonicSkip(b) 19 | } 20 | 21 | func sonicSmall(b *testing.B) { 22 | b.Helper() 23 | sonicSkip(b) 24 | } 25 | 26 | func sonicDecodeSmall(b *testing.B) { 27 | b.Helper() 28 | sonicSkip(b) 29 | } 30 | -------------------------------------------------------------------------------- /_bench/sonic_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | 3 | package bench 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/bytedance/sonic" 9 | "github.com/bytedance/sonic/encoder" 10 | ) 11 | 12 | func sonicHelloWorld(b *testing.B) { 13 | buf := make([]byte, 0, 1024) 14 | v := &HelloWorld{Message: helloWorldMessage} 15 | setupHelloWorld(b) 16 | for i := 0; i < b.N; i++ { 17 | buf = buf[:0] // reset buffer 18 | if err := encoder.EncodeInto(&buf, v, 0); err != nil { 19 | b.Fatal(err) 20 | } 21 | } 22 | } 23 | 24 | func sonicSmall(b *testing.B) { 25 | buf := make([]byte, 0, 1024) 26 | setupSmall(b) 27 | for i := 0; i < b.N; i++ { 28 | buf = buf[:0] // reset buffer 29 | if err := encoder.EncodeInto(&buf, small, 0); err != nil { 30 | b.Fatal(err) 31 | } 32 | } 33 | } 34 | 35 | func sonicDecodeSmall(b *testing.B) { 36 | data := string(setupSmall(b)) 37 | var v Small 38 | for i := 0; i < b.N; i++ { 39 | if err := sonic.UnmarshalString(data, &v); err != nil { 40 | b.Fatal(err) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /alloc_test.go: -------------------------------------------------------------------------------- 1 | //go:build !race 2 | 3 | package jx 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/go-faster/errors" 9 | ) 10 | 11 | const defaultAllocRuns = 20 12 | 13 | func zeroAlloc(t *testing.T, f func()) { 14 | t.Helper() 15 | avg := testing.AllocsPerRun(defaultAllocRuns, f) 16 | if avg > 0 { 17 | t.Errorf("Allocated %f bytes per run", avg) 18 | } 19 | } 20 | 21 | func zeroAllocDec(t *testing.T, buf []byte, f func(d *Decoder) error) { 22 | t.Helper() 23 | d := DecodeBytes(buf) 24 | zeroAlloc(t, func() { 25 | d.ResetBytes(buf) 26 | if err := f(d); err != nil { 27 | t.Fatal(err) 28 | } 29 | }) 30 | } 31 | 32 | func zeroAllocEnc(t *testing.T, f func(e *Encoder)) { 33 | t.Helper() 34 | var e Encoder 35 | zeroAlloc(t, func() { 36 | e.Reset() 37 | f(&e) 38 | }) 39 | } 40 | 41 | func zeroAllocDecStr(t *testing.T, s string, f func(d *Decoder) error) { 42 | t.Helper() 43 | zeroAllocDec(t, []byte(s), f) 44 | } 45 | 46 | func TestZeroAlloc(t *testing.T) { 47 | // Tests that checks for zero allocations. 48 | t.Run("Decoder", func(t *testing.T) { 49 | t.Run("Validate", func(t *testing.T) { 50 | zeroAllocDec(t, benchData, func(d *Decoder) error { 51 | return d.Validate() 52 | }) 53 | }) 54 | t.Run("ObjBytes", func(t *testing.T) { 55 | zeroAllocDec(t, benchData, func(d *Decoder) error { 56 | return d.Arr(func(d *Decoder) error { 57 | return d.ObjBytes(func(d *Decoder, key []byte) error { 58 | switch string(key) { 59 | case "person", "company": // ok 60 | default: 61 | return errors.New("unexpected key") 62 | } 63 | switch d.Next() { 64 | case Object: 65 | return d.ObjBytes(func(d *Decoder, key []byte) error { 66 | return d.Skip() 67 | }) 68 | default: 69 | return d.Skip() 70 | } 71 | }) 72 | }) 73 | }) 74 | }) 75 | t.Run("Int", func(t *testing.T) { 76 | zeroAllocDecStr(t, "12345", func(d *Decoder) error { 77 | v, err := d.Int() 78 | if v != 12345 { 79 | t.Fatal(v) 80 | } 81 | return err 82 | }) 83 | }) 84 | t.Run("StrBytes", func(t *testing.T) { 85 | zeroAllocDecStr(t, `"hello"`, func(d *Decoder) error { 86 | v, err := d.StrBytes() 87 | if string(v) != "hello" { 88 | t.Fatal(string(v)) 89 | } 90 | return err 91 | }) 92 | }) 93 | t.Run("ArrBigFile", func(t *testing.T) { 94 | zeroAllocDec(t, benchData, func(d *Decoder) error { 95 | return d.Arr(nil) 96 | }) 97 | }) 98 | }) 99 | t.Run("Encoder", func(t *testing.T) { 100 | t.Run("Manual", func(t *testing.T) { 101 | zeroAllocEnc(t, func(e *Encoder) { 102 | e.ObjStart() 103 | e.FieldStart("foo") 104 | e.ArrStart() 105 | e.Int(1) 106 | e.Int(2) 107 | e.Int(3) 108 | e.ArrEnd() 109 | e.ObjEnd() 110 | }) 111 | }) 112 | t.Run("Small object", func(t *testing.T) { 113 | zeroAllocEnc(t, encodeSmallObject) 114 | }) 115 | t.Run("Callback", func(t *testing.T) { 116 | zeroAllocEnc(t, encodeSmallCallback) 117 | }) 118 | }) 119 | } 120 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "embed" 5 | "path" 6 | "testing" 7 | 8 | "github.com/go-faster/errors" 9 | ) 10 | 11 | var ( 12 | //go:embed testdata/medium.json 13 | benchData []byte 14 | //go:embed testdata 15 | testdata embed.FS 16 | ) 17 | 18 | func runTestdata(fatal func(...interface{}), cb func(name string, data []byte)) { 19 | dir, err := testdata.ReadDir("testdata") 20 | if err != nil { 21 | fatal(err) 22 | } 23 | for _, e := range dir { 24 | if e.IsDir() { 25 | continue 26 | } 27 | runTestdataFile(e.Name(), fatal, cb) 28 | } 29 | } 30 | 31 | func runTestdataFile(file string, fatal func(...interface{}), cb func(name string, data []byte)) { 32 | name := path.Join("testdata", file) 33 | data, err := testdata.ReadFile(name) 34 | if err != nil { 35 | fatal(err) 36 | } 37 | cb(file, data) 38 | } 39 | 40 | func BenchmarkFile_Decode(b *testing.B) { 41 | b.ReportAllocs() 42 | b.SetBytes(int64(len(benchData))) 43 | d := Decode(nil, 4096) 44 | 45 | for n := 0; n < b.N; n++ { 46 | d.ResetBytes(benchData) 47 | if err := d.Arr(func(d *Decoder) error { 48 | return d.ObjBytes(func(d *Decoder, key []byte) error { 49 | switch string(key) { 50 | case "person", "company": // ok 51 | default: 52 | return errors.New("unexpected key") 53 | } 54 | switch d.Next() { 55 | case Object: 56 | return d.ObjBytes(func(d *Decoder, key []byte) error { 57 | switch d.Next() { 58 | case String: 59 | _, err := d.StrBytes() 60 | return err 61 | case Number: 62 | _, err := d.Num() 63 | return err 64 | case Null: 65 | return d.Null() 66 | default: 67 | return d.Skip() 68 | } 69 | }) 70 | default: 71 | return d.Skip() 72 | } 73 | }) 74 | }); err != nil { 75 | b.Fatal(err) 76 | } 77 | } 78 | } 79 | 80 | func BenchmarkValid(b *testing.B) { 81 | d := GetDecoder() 82 | runTestdata(b.Fatal, func(name string, data []byte) { 83 | b.Run(name, func(b *testing.B) { 84 | b.SetBytes(int64(len(data))) 85 | b.ReportAllocs() 86 | b.ResetTimer() 87 | 88 | for i := 0; i < b.N; i++ { 89 | d.ResetBytes(data) 90 | if err := d.Validate(); err != nil { 91 | b.Fatal(err) 92 | } 93 | } 94 | }) 95 | }) 96 | } 97 | 98 | func encodeSmallObject(e *Encoder) { 99 | e.ObjStart() 100 | e.FieldStart("data_array") 101 | e.ArrStart() 102 | e.Int(5467889) 103 | e.Int(456717) 104 | e.Int(5789935) 105 | e.ArrEnd() 106 | e.ObjEnd() 107 | } 108 | 109 | func BenchmarkEncoder_ObjStart(b *testing.B) { 110 | e := GetEncoder() 111 | encodeSmallObject(e) 112 | setBytes(b, e) 113 | if e.String() != `{"data_array":[5467889,456717,5789935]}` { 114 | b.Fatal(e) 115 | } 116 | 117 | b.ReportAllocs() 118 | for i := 0; i < b.N; i++ { 119 | e.Reset() 120 | encodeSmallObject(e) 121 | } 122 | } 123 | 124 | func encodeSmallCallback(e *Encoder) { 125 | e.Obj(func(e *Encoder) { 126 | e.Field("foo", func(e *Encoder) { 127 | e.Arr(func(e *Encoder) { 128 | e.Int(100) 129 | e.Int(200) 130 | e.Int(300) 131 | }) 132 | }) 133 | }) 134 | } 135 | 136 | func setBytes(b *testing.B, e *Encoder) { 137 | b.Helper() 138 | b.SetBytes(int64(len(e.Bytes()))) 139 | } 140 | 141 | func BenchmarkEncoder_Obj(b *testing.B) { 142 | e := GetEncoder() 143 | b.ReportAllocs() 144 | 145 | encodeSmallCallback(e) 146 | setBytes(b, e) 147 | if string(e.Bytes()) != `{"foo":[100,200,300]}` { 148 | b.Fatal("mismatch") 149 | } 150 | 151 | for i := 0; i < b.N; i++ { 152 | e.Reset() 153 | encodeSmallCallback(e) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /bool_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestTrue(t *testing.T) { 10 | should := require.New(t) 11 | iter := DecodeStr(`true`) 12 | should.True(iter.Bool()) 13 | } 14 | 15 | func TestFalse(t *testing.T) { 16 | should := require.New(t) 17 | iter := DecodeStr(`false`) 18 | should.False(iter.Bool()) 19 | } 20 | 21 | func TestWriteTrueFalse(t *testing.T) { 22 | should := require.New(t) 23 | w := GetEncoder() 24 | w.Bool(true) 25 | w.Bool(false) 26 | w.Bool(false) 27 | should.Equal("truefalsefalse", string(w.Bytes())) 28 | } 29 | -------------------------------------------------------------------------------- /crawl_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "github.com/go-faster/errors" 4 | 5 | func crawlValue(d *Decoder) error { 6 | switch d.Next() { 7 | case Null: 8 | if err := d.Null(); err != nil { 9 | return err 10 | } 11 | case Number: 12 | if _, err := d.Float64(); err != nil { 13 | return err 14 | } 15 | case Bool: 16 | if _, err := d.Bool(); err != nil { 17 | return err 18 | } 19 | case String: 20 | if _, err := d.Str(); err != nil { 21 | return err 22 | } 23 | case Array: 24 | return d.Arr(crawlValue) 25 | case Object: 26 | return d.ObjBytes(func(d *Decoder, key []byte) error { 27 | return crawlValue(d) 28 | }) 29 | default: 30 | return errors.New("invalid token") 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /dec.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // Type of json value. 8 | type Type int 9 | 10 | func (t Type) String() string { 11 | switch t { 12 | case Invalid: 13 | return "invalid" 14 | case String: 15 | return "string" 16 | case Number: 17 | return "number" 18 | case Null: 19 | return "null" 20 | case Bool: 21 | return "bool" 22 | case Array: 23 | return "array" 24 | case Object: 25 | return "object" 26 | default: 27 | return "unknown" 28 | } 29 | } 30 | 31 | const ( 32 | // Invalid json value. 33 | Invalid Type = iota 34 | // String json value, like "foo". 35 | String 36 | // Number json value, like 100 or 1.01. 37 | Number 38 | // Null json value. 39 | Null 40 | // Bool json value, true or false. 41 | Bool 42 | // Array json value, like [1, 2, 3]. 43 | Array 44 | // Object json value, like {"foo": 1}. 45 | Object 46 | ) 47 | 48 | var types []Type 49 | 50 | func init() { 51 | types = make([]Type, 256) 52 | for i := range types { 53 | types[i] = Invalid 54 | } 55 | types['"'] = String 56 | types['-'] = Number 57 | types['0'] = Number 58 | types['1'] = Number 59 | types['2'] = Number 60 | types['3'] = Number 61 | types['4'] = Number 62 | types['5'] = Number 63 | types['6'] = Number 64 | types['7'] = Number 65 | types['8'] = Number 66 | types['9'] = Number 67 | types['t'] = Bool 68 | types['f'] = Bool 69 | types['n'] = Null 70 | types['['] = Array 71 | types['{'] = Object 72 | } 73 | 74 | // Decoder decodes json. 75 | // 76 | // Can decode from io.Reader or byte slice directly. 77 | type Decoder struct { 78 | reader io.Reader 79 | 80 | // buf is current buffer. 81 | // 82 | // Contains full json if reader is nil or used as a read buffer 83 | // otherwise. 84 | buf []byte 85 | head int // offset in buf to start of current json stream 86 | tail int // offset in buf to end of current json stream 87 | 88 | streamOffset int // for reader, offset in stream to start of current buf contents 89 | depth int 90 | } 91 | 92 | const defaultBuf = 512 93 | 94 | // Decode creates a Decoder that reads json from io.Reader. 95 | func Decode(reader io.Reader, bufSize int) *Decoder { 96 | if bufSize <= 0 { 97 | bufSize = defaultBuf 98 | } 99 | return &Decoder{ 100 | reader: reader, 101 | buf: make([]byte, bufSize), 102 | } 103 | } 104 | 105 | // DecodeBytes creates a Decoder that reads json from byte slice. 106 | func DecodeBytes(input []byte) *Decoder { 107 | return &Decoder{ 108 | buf: input, 109 | tail: len(input), 110 | } 111 | } 112 | 113 | // DecodeStr creates a Decoder that reads string as json. 114 | func DecodeStr(input string) *Decoder { 115 | return DecodeBytes([]byte(input)) 116 | } 117 | 118 | func (d *Decoder) offset() int { 119 | return d.streamOffset + d.head 120 | } 121 | 122 | // Reset resets reader and underlying state, next reads will use provided io.Reader. 123 | func (d *Decoder) Reset(reader io.Reader) { 124 | d.reader = reader 125 | d.head = 0 126 | d.tail = 0 127 | d.depth = 0 128 | 129 | // Reads from reader need buffer. 130 | if cap(d.buf) == 0 { 131 | // Allocate new buffer if none. 132 | d.buf = make([]byte, defaultBuf) 133 | } 134 | if len(d.buf) == 0 { 135 | // Set buffer to full capacity if needed. 136 | d.buf = d.buf[:cap(d.buf)] 137 | } 138 | } 139 | 140 | // ResetBytes resets underlying state, next reads will use provided buffer. 141 | func (d *Decoder) ResetBytes(input []byte) { 142 | d.reader = nil 143 | d.head = 0 144 | d.tail = len(input) 145 | d.depth = 0 146 | 147 | d.buf = input 148 | } 149 | -------------------------------------------------------------------------------- /dec_arr.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/go-faster/errors" 5 | ) 6 | 7 | // Elem skips to the start of next array element, returning true boolean 8 | // if element exists. 9 | // 10 | // Can be called before or in Array. 11 | func (d *Decoder) Elem() (ok bool, err error) { 12 | c, err := d.next() 13 | if err != nil { 14 | return false, err 15 | } 16 | switch c { 17 | case '[': 18 | c, err := d.more() 19 | if err != nil { 20 | return false, err 21 | } 22 | if c != ']' { 23 | d.unread() 24 | return true, nil 25 | } 26 | return false, nil 27 | case ']': 28 | return false, nil 29 | case ',': 30 | return true, nil 31 | default: 32 | return false, errors.Wrap(badToken(c, d.offset()), `"[", "," or "]" expected`) 33 | } 34 | } 35 | 36 | // Arr decodes array and invokes callback on each array element. 37 | func (d *Decoder) Arr(f func(d *Decoder) error) error { 38 | if err := d.consume('['); err != nil { 39 | return errors.Wrap(err, `"[" expected`) 40 | } 41 | if f == nil { 42 | return d.skipArr() 43 | } 44 | if err := d.incDepth(); err != nil { 45 | return err 46 | } 47 | c, err := d.more() 48 | if err != nil { 49 | return errors.Wrap(err, `value or "]" expected`) 50 | } 51 | if c == ']' { 52 | return d.decDepth() 53 | } 54 | d.unread() 55 | if err := f(d); err != nil { 56 | return errors.Wrap(err, "callback") 57 | } 58 | 59 | c, err = d.more() 60 | if err != nil { 61 | return errors.Wrap(err, `"," or "]" expected`) 62 | } 63 | for c == ',' { 64 | // Skip whitespace before reading element. 65 | if _, err := d.next(); err != nil { 66 | return err 67 | } 68 | d.unread() 69 | if err := f(d); err != nil { 70 | return errors.Wrap(err, "callback") 71 | } 72 | if c, err = d.next(); err != nil { 73 | return err 74 | } 75 | } 76 | if c != ']' { 77 | err := badToken(c, d.offset()-1) 78 | return errors.Wrap(err, `"]" expected`) 79 | } 80 | return d.decDepth() 81 | } 82 | -------------------------------------------------------------------------------- /dec_arr_iter.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/go-faster/errors" 5 | ) 6 | 7 | // ArrIter is decoding array iterator. 8 | type ArrIter struct { 9 | d *Decoder 10 | err error 11 | closed bool 12 | comma bool 13 | } 14 | 15 | // ArrIter creates new array iterator. 16 | func (d *Decoder) ArrIter() (ArrIter, error) { 17 | if err := d.consume('['); err != nil { 18 | return ArrIter{}, errors.Wrap(err, `"[" expected`) 19 | } 20 | if err := d.incDepth(); err != nil { 21 | return ArrIter{}, err 22 | } 23 | if _, err := d.more(); err != nil { 24 | return ArrIter{}, err 25 | } 26 | d.unread() 27 | return ArrIter{d: d}, nil 28 | } 29 | 30 | // Next consumes element and returns false, if there is no elements anymore. 31 | func (i *ArrIter) Next() bool { 32 | if i.closed || i.err != nil { 33 | return false 34 | } 35 | 36 | dec := i.d 37 | c, err := dec.more() 38 | if err != nil { 39 | i.err = err 40 | return false 41 | } 42 | if c == ']' { 43 | i.closed = true 44 | i.err = dec.decDepth() 45 | return false 46 | } 47 | if i.comma { 48 | if c != ',' { 49 | err := badToken(c, dec.offset()-1) 50 | i.err = errors.Wrap(err, `"," expected`) 51 | return false 52 | } 53 | } else { 54 | dec.unread() 55 | } 56 | i.comma = true 57 | return true 58 | } 59 | 60 | // Err returns the error, if any, that was encountered during iteration. 61 | func (i *ArrIter) Err() error { 62 | return i.err 63 | } 64 | -------------------------------------------------------------------------------- /dec_arr_iter_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDecoder_ArrIter(t *testing.T) { 12 | testIter := func(d *Decoder) error { 13 | iter, err := d.ArrIter() 14 | if err != nil { 15 | return err 16 | } 17 | for iter.Next() { 18 | if err := d.Skip(); err != nil { 19 | return err 20 | } 21 | } 22 | if iter.Next() { 23 | panic("BUG") 24 | } 25 | return iter.Err() 26 | } 27 | for _, s := range testArrs { 28 | checker := require.Error 29 | if json.Valid([]byte(s)) { 30 | checker = require.NoError 31 | } 32 | 33 | d := DecodeStr(s) 34 | checker(t, testIter(d), s) 35 | } 36 | t.Run("Depth", func(t *testing.T) { 37 | d := DecodeStr(`[`) 38 | // Emulate depth 39 | d.depth = maxDepth 40 | require.ErrorIs(t, testIter(d), errMaxDepth) 41 | }) 42 | t.Run("Empty", func(t *testing.T) { 43 | d := DecodeStr(``) 44 | require.ErrorIs(t, testIter(d), io.ErrUnexpectedEOF) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /dec_arr_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "io" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDecoder_Arr(t *testing.T) { 13 | t.Run("Blank", func(t *testing.T) { 14 | d := DecodeStr(`[]`) 15 | require.NoError(t, d.Arr(nil)) 16 | }) 17 | t.Run("Invalid", func(t *testing.T) { 18 | d := DecodeStr(`{`) 19 | require.Error(t, d.Arr(nil)) 20 | }) 21 | t.Run("ErrUnexpectedEOF", func(t *testing.T) { 22 | d := DecodeStr("") 23 | require.ErrorIs(t, d.Arr(nil), io.ErrUnexpectedEOF) 24 | }) 25 | t.Run("ErrUnexpectedEOF", func(t *testing.T) { 26 | d := DecodeStr("[") 27 | require.ErrorIs(t, d.Arr(nil), io.ErrUnexpectedEOF) 28 | }) 29 | t.Run("Invalid", func(t *testing.T) { 30 | for _, s := range testArrs { 31 | checker := require.Error 32 | if json.Valid([]byte(s)) { 33 | checker = require.NoError 34 | } 35 | 36 | d := DecodeStr(s) 37 | checker(t, d.Arr(crawlValue), s) 38 | } 39 | }) 40 | t.Run("Whitespace", func(t *testing.T) { 41 | d := DecodeStr(`[1 , 2, 3 ,45, 6]`) 42 | require.NoError(t, d.Arr(func(d *Decoder) error { 43 | _, err := d.Int() 44 | return err 45 | })) 46 | }) 47 | t.Run("Depth", func(t *testing.T) { 48 | var data []byte 49 | for i := 0; i <= maxDepth; i++ { 50 | data = append(data, '[') 51 | } 52 | d := DecodeBytes(data) 53 | require.ErrorIs(t, d.Arr(nil), errMaxDepth) 54 | }) 55 | } 56 | 57 | func TestDecoder_Elem(t *testing.T) { 58 | t.Run("Blank", func(t *testing.T) { 59 | d := DecodeStr(`[]`) 60 | ok, err := d.Elem() 61 | require.NoError(t, err) 62 | require.False(t, ok) 63 | }) 64 | t.Run("Invalid", func(t *testing.T) { 65 | d := DecodeStr(`{`) 66 | ok, err := d.Elem() 67 | require.Error(t, err) 68 | require.False(t, ok) 69 | }) 70 | t.Run("EOF", func(t *testing.T) { 71 | d := DecodeStr("") 72 | ok, err := d.Elem() 73 | require.ErrorIs(t, err, io.EOF) 74 | require.False(t, ok) 75 | }) 76 | t.Run("ErrUnexpectedEOF", func(t *testing.T) { 77 | d := DecodeStr("[") 78 | ok, err := d.Elem() 79 | require.ErrorIs(t, err, io.ErrUnexpectedEOF) 80 | require.False(t, ok) 81 | }) 82 | } 83 | 84 | //go:embed testdata/bools.json 85 | var boolsData []byte 86 | 87 | func BenchmarkDecodeBools(b *testing.B) { 88 | b.Run("Callback", func(b *testing.B) { 89 | d := DecodeBytes(boolsData) 90 | r := make([]bool, 0, 100) 91 | 92 | b.SetBytes(int64(len(boolsData))) 93 | b.ReportAllocs() 94 | b.ResetTimer() 95 | 96 | for i := 0; i < b.N; i++ { 97 | r = r[:0] 98 | d.ResetBytes(boolsData) 99 | 100 | if err := d.Arr(func(d *Decoder) error { 101 | f, err := d.Bool() 102 | if err != nil { 103 | return err 104 | } 105 | r = append(r, f) 106 | return nil 107 | }); err != nil { 108 | b.Fatal(err) 109 | } 110 | } 111 | }) 112 | b.Run("Iterator", func(b *testing.B) { 113 | d := DecodeBytes(boolsData) 114 | r := make([]bool, 0, 100) 115 | 116 | b.SetBytes(int64(len(boolsData))) 117 | b.ReportAllocs() 118 | b.ResetTimer() 119 | 120 | for i := 0; i < b.N; i++ { 121 | r = r[:0] 122 | d.ResetBytes(boolsData) 123 | 124 | iter, err := d.ArrIter() 125 | if err != nil { 126 | b.Fatal(err) 127 | } 128 | for iter.Next() { 129 | v, err := d.Bool() 130 | if err != nil { 131 | b.Fatal(err) 132 | } 133 | r = append(r, v) 134 | } 135 | if err := iter.Err(); err != nil { 136 | b.Fatal(err) 137 | } 138 | } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /dec_b64.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/segmentio/asm/base64" 5 | 6 | "github.com/go-faster/errors" 7 | ) 8 | 9 | // Base64 decodes base64 encoded data from string. 10 | // 11 | // Same as encoding/json, base64.StdEncoding or RFC 4648. 12 | func (d *Decoder) Base64() ([]byte, error) { 13 | if d.Next() == Null { 14 | if err := d.Null(); err != nil { 15 | return nil, errors.Wrap(err, "read null") 16 | } 17 | return nil, nil 18 | } 19 | return d.Base64Append([]byte{}) 20 | } 21 | 22 | // Base64Append appends base64 encoded data from string. 23 | // 24 | // Same as encoding/json, base64.StdEncoding or RFC 4648. 25 | func (d *Decoder) Base64Append(b []byte) ([]byte, error) { 26 | if d.Next() == Null { 27 | if err := d.Null(); err != nil { 28 | return nil, errors.Wrap(err, "read null") 29 | } 30 | return b, nil 31 | } 32 | buf, err := d.StrBytes() 33 | if err != nil { 34 | return nil, errors.Wrap(err, "bytes") 35 | } 36 | 37 | decodedLen := base64.StdEncoding.DecodedLen(len(buf)) 38 | start := len(b) 39 | b = append(b, make([]byte, decodedLen)...) 40 | 41 | n, err := base64.StdEncoding.Decode(b[start:], buf) 42 | if err != nil { 43 | return nil, errors.Wrap(err, "decode") 44 | } 45 | 46 | return b[:start+n], nil 47 | } 48 | -------------------------------------------------------------------------------- /dec_b64_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestDecoder_Base64(t *testing.T) { 11 | t.Run("Positive", func(t *testing.T) { 12 | for _, v := range [][]byte{ 13 | []byte("foo"), 14 | {1, 2, 3, 4}, 15 | {1, 2, 3}, 16 | {1, 2}, 17 | {1}, 18 | {}, 19 | nil, 20 | } { 21 | var e Encoder 22 | e.Base64(v) 23 | 24 | d := DecodeBytes(e.Bytes()) 25 | 26 | got, err := d.Base64() 27 | require.NoError(t, err) 28 | require.Equal(t, v, got) 29 | 30 | d.ResetBytes(e.Bytes()) 31 | 32 | target := make([]byte, 0) 33 | if v == nil { 34 | // Append won't return nil, so just setting target 35 | // to nil to pass test. 36 | target = nil 37 | } 38 | got, err = d.Base64Append(target) 39 | require.NoError(t, err) 40 | require.Equal(t, v, got) 41 | } 42 | }) 43 | t.Run("Negative", func(t *testing.T) { 44 | for _, v := range []string{ 45 | `false`, 46 | `nu`, 47 | `12345`, 48 | `"foo`, 49 | `"100"`, 50 | } { 51 | t.Run(v, func(t *testing.T) { 52 | d := DecodeStr(v) 53 | 54 | _, err := d.Base64() 55 | require.Error(t, err) 56 | 57 | d = DecodeStr(v) 58 | _, err = d.Base64Append(nil) 59 | require.Error(t, err) 60 | }) 61 | } 62 | }) 63 | } 64 | 65 | func BenchmarkDecoder_Base64Append(b *testing.B) { 66 | for _, n := range []int{ 67 | 128, 68 | 256, 69 | 512, 70 | 1024, 71 | } { 72 | b.Run(fmt.Sprintf("%db", n), func(b *testing.B) { 73 | var v []byte 74 | for i := 0; i < n; i++ { 75 | v = append(v, byte(i%256)) 76 | } 77 | var e Encoder 78 | e.Base64(v) 79 | 80 | b.SetBytes(int64(n)) 81 | b.ReportAllocs() 82 | 83 | d := DecodeBytes(nil) 84 | target := make([]byte, 0, n*2) 85 | for i := 0; i < b.N; i++ { 86 | d.ResetBytes(e.Bytes()) 87 | if _, err := d.Base64Append(target); err != nil { 88 | b.Fatal(err) 89 | } 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /dec_bool.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Bool reads a json object as Bool 4 | func (d *Decoder) Bool() (bool, error) { 5 | if err := d.skipSpace(); err != nil { 6 | return false, err 7 | } 8 | 9 | var ( 10 | offset = d.offset() 11 | buf [4]byte 12 | ) 13 | if err := d.readExact4(&buf); err != nil { 14 | return false, err 15 | } 16 | 17 | switch string(buf[:]) { 18 | case "true": 19 | return true, nil 20 | case "fals": 21 | c, err := d.byte() 22 | if err != nil { 23 | return false, err 24 | } 25 | if c != 'e' { 26 | return false, badToken(c, offset+4) 27 | } 28 | return false, nil 29 | default: 30 | switch c := buf[0]; c { 31 | case 't': 32 | const encodedTrue = 't' | 'r'<<8 | 'u'<<16 | 'e'<<24 33 | return false, findInvalidToken4(buf, encodedTrue, offset) 34 | case 'f': 35 | const encodedFals = 'f' | 'a'<<8 | 'l'<<16 | 's'<<24 36 | return false, findInvalidToken4(buf, encodedFals, offset) 37 | default: 38 | return false, badToken(c, offset) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /dec_bool_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "testing" 4 | 5 | func TestDecoder_Bool(t *testing.T) { 6 | runTestCases(t, testBools, func(t *testing.T, d *Decoder) error { 7 | _, err := d.Bool() 8 | return err 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /dec_capture.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // Capture calls f and then rolls back to state before call. 9 | func (d *Decoder) Capture(f func(d *Decoder) error) error { 10 | if f == nil { 11 | return nil 12 | } 13 | 14 | if d.reader != nil { 15 | // TODO(tdakkota): May it be more efficient? 16 | var ( 17 | buf bytes.Buffer 18 | streamOffset = d.streamOffset 19 | ) 20 | reader := io.TeeReader(d.reader, &buf) 21 | defer func() { 22 | d.reader = io.MultiReader(&buf, d.reader) 23 | d.streamOffset = streamOffset 24 | }() 25 | d.reader = reader 26 | } 27 | head, tail, depth := d.head, d.tail, d.depth 28 | err := f(d) 29 | d.head, d.tail, d.depth = head, tail, depth 30 | return err 31 | } 32 | -------------------------------------------------------------------------------- /dec_depth.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "github.com/go-faster/errors" 4 | 5 | // limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9 6 | const maxDepth = 10000 7 | 8 | var errMaxDepth = errors.New("depth: maximum") 9 | 10 | func (d *Decoder) incDepth() error { 11 | d.depth++ 12 | if d.depth > maxDepth { 13 | return errMaxDepth 14 | } 15 | return nil 16 | } 17 | 18 | var errNegativeDepth = errors.New("depth: negative") 19 | 20 | func (d *Decoder) decDepth() error { 21 | d.depth-- 22 | if d.depth < 0 { 23 | return errNegativeDepth 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /dec_error.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "fmt" 4 | 5 | // badTokenErr means that Token was unexpected while decoding. 6 | type badTokenErr struct { 7 | Token byte 8 | Offset int 9 | } 10 | 11 | func (e *badTokenErr) Error() string { 12 | return fmt.Sprintf("unexpected byte %d %q at %d", e.Token, e.Token, e.Offset) 13 | } 14 | 15 | func badToken(c byte, offset int) error { 16 | return &badTokenErr{Token: c, Offset: offset} 17 | } 18 | -------------------------------------------------------------------------------- /dec_error_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_badTokenErr_Error(t *testing.T) { 10 | e := &badTokenErr{ 11 | Token: 'c', 12 | Offset: 10, 13 | } 14 | s := error(e).Error() 15 | require.Equal(t, "unexpected byte 99 'c' at 10", s) 16 | } 17 | -------------------------------------------------------------------------------- /dec_float_big.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | "math/big" 6 | 7 | "github.com/go-faster/errors" 8 | ) 9 | 10 | // BigFloat read big.Float 11 | func (d *Decoder) BigFloat() (*big.Float, error) { 12 | str, err := d.numberAppend(nil) 13 | if err != nil { 14 | return nil, errors.Wrap(err, "number") 15 | } 16 | prec := 64 17 | if len(str) > prec { 18 | prec = len(str) 19 | } 20 | val, _, err := big.ParseFloat(string(str), 10, uint(prec), big.ToZero) 21 | if err != nil { 22 | return nil, errors.Wrap(err, "float") 23 | } 24 | return val, nil 25 | } 26 | 27 | // BigInt read big.Int 28 | func (d *Decoder) BigInt() (*big.Int, error) { 29 | str, err := d.numberAppend(nil) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "number") 32 | } 33 | v := big.NewInt(0) 34 | var ok bool 35 | if v, ok = v.SetString(string(str), 10); !ok { 36 | return nil, errors.New("invalid") 37 | } 38 | return v, nil 39 | } 40 | 41 | func (d *Decoder) number() ([]byte, error) { 42 | start := d.head 43 | buf := d.buf[d.head:d.tail] 44 | for i, c := range buf { 45 | switch floatDigits[c] { 46 | case invalidCharForNumber: 47 | return nil, badToken(c, d.offset()+i) 48 | case endOfNumber: 49 | // End of number. 50 | d.head += i 51 | return d.buf[start:d.head], nil 52 | default: 53 | continue 54 | } 55 | } 56 | // Buffer is number within head:tail. 57 | d.head = d.tail 58 | return d.buf[start:d.tail], nil 59 | } 60 | 61 | func (d *Decoder) numberAppend(b []byte) ([]byte, error) { 62 | for { 63 | r, err := d.number() 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | b = append(b, r...) 69 | if d.head != d.tail { 70 | return b, nil 71 | } 72 | 73 | if err := d.read(); err != nil { 74 | if err == io.EOF { 75 | return b, nil 76 | } 77 | return b, err 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /dec_int.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func (d *Decoder) int(size int) (int, error) { 8 | switch size { 9 | case 8: 10 | v, err := d.Int8() 11 | return int(v), err 12 | case 16: 13 | v, err := d.Int16() 14 | return int(v), err 15 | case 32: 16 | v, err := d.Int32() 17 | return int(v), err 18 | default: 19 | v, err := d.Int64() 20 | return int(v), err 21 | } 22 | } 23 | 24 | // Int reads int. 25 | func (d *Decoder) Int() (int, error) { 26 | return d.int(strconv.IntSize) 27 | } 28 | 29 | func (d *Decoder) uint(size int) (uint, error) { 30 | switch size { 31 | case 8: 32 | v, err := d.UInt8() 33 | return uint(v), err 34 | case 16: 35 | v, err := d.UInt16() 36 | return uint(v), err 37 | case 32: 38 | v, err := d.UInt32() 39 | return uint(v), err 40 | default: 41 | v, err := d.UInt64() 42 | return uint(v), err 43 | } 44 | } 45 | 46 | // UInt reads uint. 47 | func (d *Decoder) UInt() (uint, error) { 48 | return d.uint(strconv.IntSize) 49 | } 50 | -------------------------------------------------------------------------------- /dec_int_fuzz_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | "golang.org/x/exp/constraints" 9 | ) 10 | 11 | func fuzzCallback[Int constraints.Signed](decoder func(*Decoder) (Int, error)) func(*testing.T, Int) { 12 | return func(t *testing.T, expected Int) { 13 | a := require.New(t) 14 | buf := make([]byte, 0, 32) 15 | buf = strconv.AppendInt(buf, int64(expected), 10) 16 | 17 | d := DecodeBytes(buf) 18 | got, err := decoder(d) 19 | a.NoError(err) 20 | a.Equal(expected, got) 21 | } 22 | } 23 | 24 | func FuzzDecoderInt8(f *testing.F) { f.Fuzz(fuzzCallback[int8]((*Decoder).Int8)) } 25 | func FuzzDecoderInt16(f *testing.F) { f.Fuzz(fuzzCallback[int16]((*Decoder).Int16)) } 26 | func FuzzDecoderInt32(f *testing.F) { f.Fuzz(fuzzCallback[int32]((*Decoder).Int32)) } 27 | func FuzzDecoderInt64(f *testing.F) { f.Fuzz(fuzzCallback[int64]((*Decoder).Int64)) } 28 | -------------------------------------------------------------------------------- /dec_null.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Null reads a json object as null and 4 | // returns whether it's a null or not. 5 | func (d *Decoder) Null() error { 6 | if err := d.skipSpace(); err != nil { 7 | return err 8 | } 9 | 10 | var ( 11 | offset = d.offset() 12 | buf [4]byte 13 | ) 14 | if err := d.readExact4(&buf); err != nil { 15 | return err 16 | } 17 | 18 | if string(buf[:]) != "null" { 19 | const encodedNull = 'n' | 'u'<<8 | 'l'<<16 | 'l'<<24 20 | return findInvalidToken4(buf, encodedNull, offset) 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /dec_null_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "testing" 4 | 5 | func TestDecoder_Null(t *testing.T) { 6 | runTestCases(t, []string{ 7 | "", 8 | "nope", 9 | "nul", 10 | "nil", 11 | "nul\x00", 12 | "null", 13 | }, func(t *testing.T, d *Decoder) error { 14 | return d.Null() 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /dec_num.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/go-faster/errors" 5 | ) 6 | 7 | // Num decodes number. 8 | // 9 | // Do not retain returned value, it references underlying buffer. 10 | func (d *Decoder) Num() (Num, error) { 11 | return d.num(nil, false) 12 | } 13 | 14 | // NumAppend appends number. 15 | func (d *Decoder) NumAppend(v Num) (Num, error) { 16 | return d.num(v, true) 17 | } 18 | 19 | // num decodes number. 20 | func (d *Decoder) num(v Num, forceAppend bool) (Num, error) { 21 | switch d.Next() { 22 | case String: 23 | offset := d.offset() 24 | start := d.head 25 | 26 | str, err := d.str(value{raw: true}) 27 | if err != nil { 28 | return Num{}, errors.Wrap(err, "str") 29 | } 30 | 31 | // Validate number. 32 | { 33 | d := Decoder{} 34 | d.ResetBytes(str.buf) 35 | 36 | c, err := d.next() 37 | if err != nil { 38 | return Num{}, err 39 | } 40 | switch c { 41 | case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-': 42 | d.unread() 43 | 44 | if err := d.skipNumber(); err != nil { 45 | return Num{}, errors.Wrap(err, "skip number") 46 | } 47 | default: 48 | return nil, badToken(c, offset) 49 | } 50 | } 51 | 52 | // If string is escaped or decoder is streaming, copy it. 53 | if !str.raw || forceAppend { 54 | v = append(v, '"') 55 | v = append(v, str.buf...) 56 | v = append(v, '"') 57 | return v, nil 58 | } 59 | return d.buf[start:d.head], nil 60 | case Number: // float or integer 61 | if forceAppend { 62 | raw, err := d.RawAppend(Raw(v)) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return Num(raw), nil 67 | } 68 | 69 | raw, err := d.Raw() 70 | if err != nil { 71 | return nil, err 72 | } 73 | return Num(raw), nil 74 | default: 75 | return v, errors.Errorf("unexpected %s", d.Next()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /dec_num_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func testDecoderNum(t *testing.T, num func(*Decoder) (Num, error)) { 14 | t.Run("Cases", func(t *testing.T) { 15 | runTestCases(t, testNumbers, func(t *testing.T, d *Decoder) error { 16 | if _, err := num(d); err != nil { 17 | return err 18 | } 19 | if err := d.Skip(); err != nil { 20 | if err != io.EOF && err != io.ErrUnexpectedEOF { 21 | return err 22 | } 23 | } 24 | return nil 25 | }) 26 | }) 27 | 28 | testNum := func(inputs []string, cb func(input string, t *testing.T, d *Decoder)) func(t *testing.T) { 29 | return func(t *testing.T) { 30 | for i, input := range inputs { 31 | input := input 32 | t.Run(fmt.Sprintf("Test%d", i+1), testBufferReader(input, func(t *testing.T, d *Decoder) { 33 | cb(input, t, d) 34 | })) 35 | } 36 | } 37 | } 38 | 39 | t.Run("StrEscape", testNum([]string{ 40 | `"\u0030"`, // hex 30 = dec 48 = '0' 41 | `"\u002d\u0031\u0030\u0030"`, // "-100", but escaped 42 | }, func(input string, t *testing.T, d *Decoder) { 43 | _, err := num(d) 44 | require.NoErrorf(t, err, "input: %q", input) 45 | })) 46 | t.Run("Positive", testNum([]string{ 47 | `100`, 48 | `100.0`, 49 | `-100.0`, 50 | `-100`, 51 | `"-100"`, 52 | `"-100.0"`, 53 | }, func(input string, t *testing.T, d *Decoder) { 54 | v, err := num(d) 55 | require.NoErrorf(t, err, "input: %q", input) 56 | require.Equalf(t, input, v.String(), "input: %q", input) 57 | })) 58 | t.Run("Negative", testNum([]string{ 59 | `1.00.0`, 60 | `"-100`, 61 | `""`, 62 | `"-100.0.0"`, 63 | "false", 64 | `"false"`, 65 | }, func(input string, t *testing.T, d *Decoder) { 66 | _, err := num(d) 67 | require.Errorf(t, err, "input: %q", input) 68 | })) 69 | } 70 | 71 | func TestDecoder_Num(t *testing.T) { 72 | testDecoderNum(t, (*Decoder).Num) 73 | } 74 | 75 | func TestDecoder_NumAppend(t *testing.T) { 76 | testDecoderNum(t, func(d *Decoder) (Num, error) { 77 | return d.NumAppend(nil) 78 | }) 79 | } 80 | 81 | func BenchmarkDecoder_Num(b *testing.B) { 82 | number := strconv.FormatInt(1234567890421, 10) 83 | // escapeHex escapes the number as a string in \uXXXX format. 84 | escapeHex := func(number string) string { 85 | var b strings.Builder 86 | b.WriteByte('"') 87 | for _, c := range []byte(number) { 88 | b.WriteString("\\u00") 89 | b.WriteString(strconv.FormatInt(int64(c), 16)) 90 | } 91 | b.WriteByte('"') 92 | return b.String() 93 | } 94 | 95 | for _, bt := range []struct { 96 | name string 97 | input string 98 | }{ 99 | {`Number`, number}, 100 | {`String`, strconv.Quote(number)}, 101 | {`EscapedString`, escapeHex(number)}, 102 | } { 103 | bt := bt 104 | b.Run(bt.name, func(b *testing.B) { 105 | b.Run("Buffer", func(b *testing.B) { 106 | var ( 107 | input = []byte(bt.input) 108 | d = DecodeBytes(input) 109 | err error 110 | ) 111 | 112 | b.ReportAllocs() 113 | b.ResetTimer() 114 | 115 | for i := 0; i < b.N; i++ { 116 | d.ResetBytes(input) 117 | _, err = d.Num() 118 | } 119 | 120 | if err != nil { 121 | b.Fatal(err, bt.input) 122 | } 123 | }) 124 | b.Run("Reader", func(b *testing.B) { 125 | var ( 126 | r = strings.NewReader(bt.input) 127 | d = Decode(r, 512) 128 | err error 129 | ) 130 | 131 | b.ReportAllocs() 132 | b.ResetTimer() 133 | 134 | for i := 0; i < b.N; i++ { 135 | r.Reset(bt.input) 136 | d.Reset(r) 137 | _, err = d.Num() 138 | } 139 | 140 | if err != nil { 141 | b.Fatal(err, bt.input) 142 | } 143 | }) 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /dec_obj.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/go-faster/errors" 5 | ) 6 | 7 | // ObjBytes calls f for every key in object, using byte slice as key. 8 | // 9 | // The key value is valid only until f is not returned. 10 | func (d *Decoder) ObjBytes(f func(d *Decoder, key []byte) error) error { 11 | if err := d.consume('{'); err != nil { 12 | return errors.Wrap(err, `"{" expected`) 13 | } 14 | if f == nil { 15 | return d.skipObj() 16 | } 17 | if err := d.incDepth(); err != nil { 18 | return err 19 | } 20 | c, err := d.more() 21 | if err != nil { 22 | return errors.Wrap(err, `'"' or "}" expected`) 23 | } 24 | if c == '}' { 25 | return d.decDepth() 26 | } 27 | d.unread() 28 | // Do not reference internal buffer for key if decoder is not buffered. 29 | // 30 | // Otherwise, subsequent reads may overwrite the key. 31 | // 32 | // See https://github.com/go-faster/jx/pull/62. 33 | isBuffer := d.reader == nil 34 | 35 | k, err := d.str(value{raw: isBuffer}) 36 | if err != nil { 37 | return errors.Wrap(err, "field name") 38 | } 39 | if err := d.consume(':'); err != nil { 40 | return errors.Wrap(err, `":" expected`) 41 | } 42 | // Skip whitespace. 43 | if _, err = d.more(); err != nil { 44 | return err 45 | } 46 | d.unread() 47 | if err := f(d, k.buf); err != nil { 48 | return errors.Wrap(err, "callback") 49 | } 50 | 51 | c, err = d.more() 52 | if err != nil { 53 | return errors.Wrap(err, `"," or "}" expected`) 54 | } 55 | for c == ',' { 56 | k, err := d.str(value{raw: isBuffer}) 57 | if err != nil { 58 | return errors.Wrap(err, "field name") 59 | } 60 | if err := d.consume(':'); err != nil { 61 | return errors.Wrap(err, `":" expected`) 62 | } 63 | // Check that value exists. 64 | if _, err = d.more(); err != nil { 65 | return err 66 | } 67 | d.unread() 68 | if err := f(d, k.buf); err != nil { 69 | return errors.Wrap(err, "callback") 70 | } 71 | if c, err = d.more(); err != nil { 72 | return err 73 | } 74 | } 75 | if c != '}' { 76 | err := badToken(c, d.offset()-1) 77 | return errors.Wrap(err, `"}" expected`) 78 | } 79 | return d.decDepth() 80 | } 81 | 82 | // Obj reads json object, calling f on each field. 83 | // 84 | // Use ObjBytes to reduce heap allocations for keys. 85 | func (d *Decoder) Obj(f func(d *Decoder, key string) error) error { 86 | if f == nil { 87 | // Skipping object. 88 | return d.ObjBytes(nil) 89 | } 90 | return d.ObjBytes(func(d *Decoder, key []byte) error { 91 | return f(d, string(key)) 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /dec_obj_iter.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "github.com/go-faster/errors" 4 | 5 | // ObjIter is decoding object iterator. 6 | type ObjIter struct { 7 | d *Decoder 8 | key []byte 9 | err error 10 | isBuffer bool 11 | closed bool 12 | comma bool 13 | } 14 | 15 | // ObjIter creates new object iterator. 16 | func (d *Decoder) ObjIter() (ObjIter, error) { 17 | if err := d.consume('{'); err != nil { 18 | return ObjIter{}, errors.Wrap(err, `"{" expected`) 19 | } 20 | if err := d.incDepth(); err != nil { 21 | return ObjIter{}, err 22 | } 23 | if _, err := d.more(); err != nil { 24 | return ObjIter{}, err 25 | } 26 | d.unread() 27 | return ObjIter{d: d, isBuffer: d.reader == nil}, nil 28 | } 29 | 30 | // Key returns current key. 31 | // 32 | // Key call must be preceded by a call to Next. 33 | func (i *ObjIter) Key() []byte { 34 | return i.key 35 | } 36 | 37 | // Next consumes element and returns false, if there is no elements anymore. 38 | func (i *ObjIter) Next() bool { 39 | if i.closed || i.err != nil { 40 | return false 41 | } 42 | 43 | dec := i.d 44 | c, err := dec.more() 45 | if err != nil { 46 | i.err = err 47 | return false 48 | } 49 | if c == '}' { 50 | i.closed = true 51 | i.err = dec.decDepth() 52 | return false 53 | } 54 | if i.comma { 55 | if c != ',' { 56 | err := badToken(c, dec.offset()-1) 57 | i.err = errors.Wrap(err, `"," expected`) 58 | return false 59 | } 60 | } else { 61 | dec.unread() 62 | } 63 | 64 | k, err := dec.str(value{raw: i.isBuffer}) 65 | if err != nil { 66 | i.err = errors.Wrap(err, "field name") 67 | return false 68 | } 69 | if err := dec.consume(':'); err != nil { 70 | i.err = errors.Wrap(err, `":" expected`) 71 | return false 72 | } 73 | // Skip whitespace. 74 | if _, err = dec.more(); err != nil { 75 | err := badToken(c, dec.offset()-1) 76 | i.err = errors.Wrap(err, `"," or "}" expected`) 77 | return false 78 | } 79 | dec.unread() 80 | 81 | i.comma = true 82 | i.key = k.buf 83 | 84 | return true 85 | } 86 | 87 | // Err returns the error, if any, that was encountered during iteration. 88 | func (i *ObjIter) Err() error { 89 | return i.err 90 | } 91 | -------------------------------------------------------------------------------- /dec_obj_iter_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/go-faster/errors" 13 | ) 14 | 15 | func TestDecoder_ObjIter(t *testing.T) { 16 | testIter := func(d *Decoder) error { 17 | iter, err := d.ObjIter() 18 | if err != nil { 19 | return err 20 | } 21 | for iter.Next() { 22 | if err := d.Skip(); err != nil { 23 | return err 24 | } 25 | } 26 | if iter.Next() { 27 | panic("BUG") 28 | } 29 | if err := iter.Err(); err != nil { 30 | return err 31 | } 32 | 33 | // Check for any trialing json. 34 | if d.head != d.tail { 35 | if err := d.Skip(); err != io.EOF { 36 | return errors.Wrap(err, "unexpected trialing data") 37 | } 38 | } 39 | return nil 40 | } 41 | for i, s := range testObjs { 42 | s := s 43 | t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { 44 | checker := require.Error 45 | if json.Valid([]byte(s)) { 46 | checker = require.NoError 47 | } 48 | 49 | d := DecodeStr(s) 50 | checker(t, testIter(d), s) 51 | }) 52 | } 53 | t.Run("Depth", func(t *testing.T) { 54 | d := DecodeStr(`{`) 55 | // Emulate depth 56 | d.depth = maxDepth 57 | require.ErrorIs(t, testIter(d), errMaxDepth) 58 | }) 59 | t.Run("Empty", func(t *testing.T) { 60 | d := DecodeStr(``) 61 | require.ErrorIs(t, testIter(d), io.ErrUnexpectedEOF) 62 | }) 63 | t.Run("Key", testBufferReader(`{"foo":1,"bar":1,"baz":1}`, func(t *testing.T, d *Decoder) { 64 | a := require.New(t) 65 | 66 | iter, err := d.ObjIter() 67 | a.NoError(err) 68 | 69 | var r []string 70 | for iter.Next() { 71 | r = append(r, string(iter.Key())) 72 | a.NoError(d.Skip()) 73 | } 74 | a.False(iter.Next()) 75 | a.NoError(iter.Err()) 76 | 77 | a.Equal([]string{"foo", "bar", "baz"}, r) 78 | })) 79 | } 80 | 81 | func TestDecoderObjIterIssue62(t *testing.T) { 82 | a := require.New(t) 83 | 84 | const input = `{"1":1,"2":2}` 85 | 86 | // Force decoder to read only first 4 bytes of input. 87 | d := Decode(strings.NewReader(input), 4) 88 | 89 | iter, err := d.ObjIter() 90 | a.NoError(err) 91 | 92 | actual := map[string]int{} 93 | for iter.Next() { 94 | val, err := d.Int() 95 | a.NoError(err) 96 | actual[string(iter.Key())] = val 97 | } 98 | a.Equal(map[string]int{ 99 | "1": 1, 100 | "2": 2, 101 | }, actual) 102 | } 103 | -------------------------------------------------------------------------------- /dec_obj_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDecoder_ObjBytes(t *testing.T) { 13 | t.Run("Object", func(t *testing.T) { 14 | i := DecodeStr(`{ "id" :1 , "randomNumber" : 10 }`) 15 | met := map[string]struct{}{} 16 | require.NoError(t, i.ObjBytes(func(i *Decoder, key []byte) error { 17 | switch string(key) { 18 | case "id": 19 | v, err := i.Int64() 20 | assert.NoError(t, err) 21 | assert.Equal(t, int64(1), v) 22 | met["id"] = struct{}{} 23 | case "randomNumber": 24 | v, err := i.Int64() 25 | if err != nil { 26 | return err 27 | } 28 | assert.Equal(t, int64(10), v) 29 | met["randomNumber"] = struct{}{} 30 | } 31 | return nil 32 | })) 33 | if len(met) != 2 { 34 | t.Error("not all keys met") 35 | } 36 | }) 37 | t.Run("Depth", func(t *testing.T) { 38 | var input []byte 39 | for i := 0; i <= maxDepth; i++ { 40 | input = append(input, `{"1":`...) 41 | } 42 | d := DecodeBytes(input) 43 | require.ErrorIs(t, d.ObjBytes(func(d *Decoder, key []byte) error { 44 | return crawlValue(d) 45 | }), errMaxDepth) 46 | }) 47 | t.Run("Invalid", func(t *testing.T) { 48 | for _, s := range testObjs { 49 | checker := require.Error 50 | if json.Valid([]byte(s)) { 51 | continue 52 | } 53 | 54 | d := DecodeStr(s) 55 | err := d.ObjBytes(func(d *Decoder, key []byte) error { 56 | return crawlValue(d) 57 | }) 58 | if err == nil && len(d.buf) > 0 { 59 | // FIXME(tdakkota): fix cases like {"hello":{}}} 60 | continue 61 | } 62 | checker(t, err, s) 63 | } 64 | }) 65 | } 66 | 67 | func TestDecoderObjBytesIssue62(t *testing.T) { 68 | a := require.New(t) 69 | 70 | const input = `{"1":1,"2":2}` 71 | 72 | // Force decoder to read only first 4 bytes of input. 73 | d := Decode(strings.NewReader(input), 4) 74 | 75 | actual := map[string]int{} 76 | a.NoError(d.ObjBytes(func(d *Decoder, key []byte) error { 77 | val, err := d.Int() 78 | if err != nil { 79 | return err 80 | } 81 | actual[string(key)] = val 82 | return nil 83 | })) 84 | a.Equal(map[string]int{ 85 | "1": 1, 86 | "2": 2, 87 | }, actual) 88 | } 89 | -------------------------------------------------------------------------------- /dec_raw.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/go-faster/errors" 7 | ) 8 | 9 | type rawReader struct { 10 | // internal buffer, may be reference to *Decoder.buf. 11 | buf []byte 12 | // if true, buf is reference to *Decoder.buf. 13 | captured bool 14 | orig io.Reader 15 | } 16 | 17 | func (r *rawReader) Read(p []byte) (n int, err error) { 18 | if r.captured { 19 | // Make a copy. 20 | r.buf = append([]byte(nil), r.buf...) 21 | r.captured = false 22 | } 23 | n, err = r.orig.Read(p) 24 | if n > 0 { 25 | r.buf = append(r.buf, p[:n]...) 26 | } 27 | return n, err 28 | } 29 | 30 | // Raw is like Skip(), but saves and returns skipped value as raw json. 31 | // 32 | // Do not retain returned value, it references underlying buffer. 33 | func (d *Decoder) Raw() (Raw, error) { 34 | start := d.head 35 | if orig := d.reader; orig != nil { 36 | rr := &rawReader{ 37 | buf: d.buf[start:d.tail], 38 | captured: true, 39 | orig: orig, 40 | } 41 | d.reader = rr 42 | defer func() { 43 | d.reader = orig 44 | }() 45 | 46 | if err := d.Skip(); err != nil { 47 | return nil, errors.Wrap(err, "skip") 48 | } 49 | 50 | unread := d.tail - d.head 51 | raw := rr.buf 52 | raw = raw[:len(raw)-unread] 53 | return raw, nil 54 | } 55 | 56 | if err := d.Skip(); err != nil { 57 | return nil, errors.Wrap(err, "skip") 58 | } 59 | 60 | return d.buf[start:d.head], nil 61 | } 62 | 63 | // RawAppend is Raw that appends saved raw json value to buf. 64 | func (d *Decoder) RawAppend(buf Raw) (Raw, error) { 65 | raw, err := d.Raw() 66 | if err != nil { 67 | return nil, err 68 | } 69 | return append(buf, raw...), err 70 | } 71 | 72 | // Raw json value. 73 | type Raw []byte 74 | 75 | // Type of Raw json value. 76 | func (r Raw) Type() Type { 77 | d := Decoder{buf: r, tail: len(r)} 78 | return d.Next() 79 | } 80 | 81 | func (r Raw) String() string { return string(r) } 82 | -------------------------------------------------------------------------------- /dec_read.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | "math/bits" 6 | ) 7 | 8 | // Next gets Type of relatively next json element 9 | func (d *Decoder) Next() Type { 10 | v, err := d.next() 11 | if err == nil { 12 | d.unread() 13 | } 14 | return types[v] 15 | } 16 | 17 | var spaceSet = [256]byte{ 18 | ' ': 1, '\n': 1, '\t': 1, '\r': 1, 19 | } 20 | 21 | func (d *Decoder) consume(c byte) (err error) { 22 | for { 23 | buf := d.buf[d.head:d.tail] 24 | for i, got := range buf { 25 | switch spaceSet[got] { 26 | default: 27 | if c != got { 28 | return badToken(got, d.offset()+i) 29 | } 30 | d.head += i + 1 31 | return nil 32 | case 1: 33 | continue 34 | } 35 | } 36 | if err = d.read(); err != nil { 37 | if err == io.EOF { 38 | return io.ErrUnexpectedEOF 39 | } 40 | return err 41 | } 42 | } 43 | } 44 | 45 | // more is next but io.EOF is unexpected. 46 | func (d *Decoder) more() (byte, error) { 47 | c, err := d.next() 48 | if err == io.EOF { 49 | err = io.ErrUnexpectedEOF 50 | } 51 | return c, err 52 | } 53 | 54 | // next reads next non-whitespace token or error. 55 | func (d *Decoder) next() (byte, error) { 56 | for { 57 | buf := d.buf[d.head:d.tail] 58 | for i, c := range buf { 59 | switch spaceSet[c] { 60 | default: 61 | d.head += i + 1 62 | return c, nil 63 | case 1: 64 | continue 65 | } 66 | } 67 | if err := d.read(); err != nil { 68 | return 0, err 69 | } 70 | } 71 | } 72 | 73 | // peek returns next byte without advancing. 74 | func (d *Decoder) peek() (byte, error) { 75 | if d.head == d.tail { 76 | if err := d.read(); err != nil { 77 | return 0, err 78 | } 79 | } 80 | c := d.buf[d.head] 81 | return c, nil 82 | } 83 | 84 | func (d *Decoder) byte() (byte, error) { 85 | if d.head == d.tail { 86 | err := d.read() 87 | if err == io.EOF { 88 | err = io.ErrUnexpectedEOF 89 | } 90 | if err != nil { 91 | return 0, err 92 | } 93 | } 94 | c := d.buf[d.head] 95 | d.head++ 96 | return c, nil 97 | } 98 | 99 | func (d *Decoder) read() error { 100 | if d.reader == nil { 101 | d.head = d.tail 102 | return io.EOF 103 | } 104 | 105 | n, err := d.reader.Read(d.buf) 106 | switch err { 107 | case nil: 108 | case io.EOF: 109 | if n > 0 { 110 | break 111 | } 112 | fallthrough 113 | default: 114 | return err 115 | } 116 | 117 | d.streamOffset += d.tail 118 | d.head = 0 119 | d.tail = n 120 | return nil 121 | } 122 | 123 | func (d *Decoder) readAtLeast(n int) error { 124 | if d.reader == nil { 125 | d.head = d.tail 126 | return io.ErrUnexpectedEOF 127 | } 128 | 129 | if need := n - len(d.buf); need > 0 { 130 | d.buf = append(d.buf, make([]byte, need)...) 131 | } 132 | n, err := io.ReadAtLeast(d.reader, d.buf, n) 133 | if err != nil { 134 | if err == io.EOF && n == 0 { 135 | return io.ErrUnexpectedEOF 136 | } 137 | return err 138 | } 139 | 140 | d.streamOffset += d.tail 141 | d.head = 0 142 | d.tail = n 143 | return nil 144 | } 145 | 146 | func (d *Decoder) unread() { d.head-- } 147 | 148 | func (d *Decoder) readExact4(b *[4]byte) error { 149 | if buf := d.buf[d.head:d.tail]; len(buf) >= len(b) { 150 | d.head += copy(b[:], buf[:4]) 151 | return nil 152 | } 153 | 154 | n := copy(b[:], d.buf[d.head:d.tail]) 155 | if err := d.readAtLeast(len(b) - n); err != nil { 156 | return err 157 | } 158 | d.head += copy(b[n:], d.buf[d.head:d.tail]) 159 | return nil 160 | } 161 | 162 | func findInvalidToken4(buf [4]byte, mask uint32, offset int) error { 163 | c := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 164 | idx := bits.TrailingZeros32(c^mask) / 8 165 | return badToken(buf[idx], offset+idx) 166 | } 167 | -------------------------------------------------------------------------------- /dec_read_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDecoder_readAtLeast(t *testing.T) { 12 | a := require.New(t) 13 | d := Decode(strings.NewReader("aboba"), 1) 14 | a.NoError(d.readAtLeast(4)) 15 | a.Equal(d.buf[d.head:d.tail], []byte("abob")) 16 | } 17 | 18 | func TestDecoder_consume(t *testing.T) { 19 | r := errReader{} 20 | d := Decode(r, 1) 21 | require.ErrorIs(t, d.consume('"'), r.Err()) 22 | } 23 | 24 | func TestDecoder_Next(t *testing.T) { 25 | d := DecodeBytes(nil) 26 | d.Next() 27 | d.Next() 28 | d.Next() 29 | 30 | _, err := d.Str() 31 | require.ErrorIs(t, err, io.ErrUnexpectedEOF) 32 | } 33 | -------------------------------------------------------------------------------- /dec_skip_bench_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type TestResp struct { 8 | Code uint64 9 | } 10 | 11 | func BenchmarkSkip(b *testing.B) { 12 | input := []byte(` 13 | { 14 | "_shards":{ 15 | "total" : 5, 16 | "successful" : 5, 17 | "failed" : 0 18 | }, 19 | "hits":{ 20 | "total" : 1, 21 | "hits" : [ 22 | { 23 | "_index" : "twitter", 24 | "_type" : "tweet", 25 | "_id" : "1", 26 | "_source" : { 27 | "user" : "kimchy", 28 | "postDate" : "2009-11-15T14:12:12", 29 | "message" : "trying out Elasticsearch" 30 | } 31 | } 32 | ] 33 | }, 34 | "code": 200 35 | }`) 36 | for n := 0; n < b.N; n++ { 37 | result := TestResp{} 38 | iter := DecodeBytes(input) 39 | if err := iter.ObjBytes(func(i *Decoder, key []byte) error { 40 | switch string(key) { 41 | case "code": 42 | v, err := iter.UInt64() 43 | result.Code = v 44 | return err 45 | default: 46 | return iter.Skip() 47 | } 48 | }); err != nil { 49 | b.Fatal(err) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /dec_skip_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestDecoderSkipArrayNested(t *testing.T) { 13 | runTestCases(t, []string{ 14 | `[-0.12, "stream"]`, 15 | `["hello", "stream"]`, 16 | `[null , "stream"]`, 17 | `[true , "stream"]`, 18 | `[false , "stream"]`, 19 | `[[1, [2, [3], 4]], "stream"]`, 20 | `[ [ ], "stream"]`, 21 | }, func(t *testing.T, d *Decoder) error { 22 | var err error 23 | a := require.New(t) 24 | _, err = d.Elem() 25 | a.NoError(err) 26 | err = d.Skip() 27 | a.NoError(err) 28 | _, err = d.Elem() 29 | a.NoError(err) 30 | 31 | s, err := d.Str() 32 | a.NoError(err) 33 | a.Equal("stream", s) 34 | return nil 35 | }) 36 | } 37 | 38 | func TestDecoderSkipNested(t *testing.T) { 39 | d := DecodeStr(`[ {"a" : [{"stream": "c"}], "d": 102 }, "stream"]`) 40 | if _, err := d.Elem(); err != nil { 41 | t.Fatal(err) 42 | } 43 | require.NoError(t, d.Skip()) 44 | if _, err := d.Elem(); err != nil { 45 | t.Fatal(err) 46 | } 47 | s, err := d.Str() 48 | require.NoError(t, err) 49 | require.Equal(t, "stream", s) 50 | } 51 | 52 | func TestDecoderSkipSimpleNested(t *testing.T) { 53 | d := DecodeStr(`["foo", "bar", "baz"]`) 54 | require.NoError(t, d.Skip()) 55 | } 56 | 57 | func TestDecoder_skipNumber(t *testing.T) { 58 | inputs := []string{ 59 | `0`, 60 | `120`, 61 | `0.`, 62 | `0.0e`, 63 | `0.0e+1`, 64 | } 65 | sr := strings.NewReader("") 66 | er := &errReader{} 67 | for i, tt := range inputs { 68 | t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) { 69 | sr.Reset(tt) 70 | d := Decode(io.MultiReader(sr, er), len(tt)) 71 | require.NoError(t, d.read()) 72 | require.Error(t, d.skipNumber()) 73 | }) 74 | } 75 | } 76 | 77 | func TestDecoder_SkipObjDepth(t *testing.T) { 78 | var input []byte 79 | for i := 0; i <= maxDepth; i++ { 80 | input = append(input, `{"1":`...) 81 | } 82 | require.Error(t, DecodeBytes(input).Skip()) 83 | } 84 | -------------------------------------------------------------------------------- /dec_validate.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/go-faster/errors" 7 | ) 8 | 9 | // Validate consumes all input, validating that input is a json object 10 | // without any trialing data. 11 | func (d *Decoder) Validate() error { 12 | // First encountered value skip should consume all buffer. 13 | if err := d.Skip(); err != nil { 14 | return errors.Wrap(err, "consume") 15 | } 16 | // Check for any trialing json. 17 | if err := d.Skip(); err != io.EOF { 18 | return errors.Wrap(err, "unexpected trialing data") 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /dec_validate_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestDecoder_Validate(t *testing.T) { 10 | d := GetDecoder() 11 | runTestdata(t.Fatal, func(name string, data []byte) { 12 | t.Run(name, func(t *testing.T) { 13 | d.ResetBytes(data) 14 | require.NoError(t, d.Validate()) 15 | }) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /enc_b64.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Base64 encodes data as standard base64 encoded string. 4 | // 5 | // Same as encoding/json, base64.StdEncoding or RFC 4648. 6 | func (e *Encoder) Base64(data []byte) bool { 7 | return e.comma() || 8 | e.w.Base64(data) 9 | } 10 | -------------------------------------------------------------------------------- /enc_b64_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestEncoder_Base64(t *testing.T) { 10 | t.Run("Values", func(t *testing.T) { 11 | for i, s := range [][]byte{ 12 | []byte(`1`), 13 | []byte(`12`), 14 | []byte(`2345`), 15 | {1, 2, 3, 4, 5, 6}, 16 | 17 | bytes.Repeat([]byte{1}, encoderBufSize-1), 18 | bytes.Repeat([]byte{1}, encoderBufSize), 19 | bytes.Repeat([]byte{1}, encoderBufSize+1), 20 | } { 21 | s := s 22 | t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { 23 | requireCompat(t, func(e *Encoder) { 24 | e.Base64(s) 25 | }, s) 26 | }) 27 | } 28 | }) 29 | t.Run("Zeroes", func(t *testing.T) { 30 | t.Run("Nil", func(t *testing.T) { 31 | s := []byte(nil) 32 | requireCompat(t, func(e *Encoder) { 33 | e.Base64(s) 34 | }, s) 35 | }) 36 | t.Run("ZeroLen", func(t *testing.T) { 37 | s := make([]byte, 0) 38 | requireCompat(t, func(e *Encoder) { 39 | e.Base64(s) 40 | }, s) 41 | }) 42 | }) 43 | } 44 | 45 | func BenchmarkEncoder_Base64(b *testing.B) { 46 | for _, n := range []int{ 47 | 128, 48 | 256, 49 | 512, 50 | 1024, 51 | } { 52 | b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { 53 | var v []byte 54 | for i := 0; i < n; i++ { 55 | v = append(v, byte(i%256)) 56 | } 57 | 58 | b.ReportAllocs() 59 | b.SetBytes(int64(n)) 60 | var e Encoder 61 | for i := 0; i < b.N; i++ { 62 | e.Base64(v) 63 | e.Reset() 64 | } 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /enc_comma.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // begin should be called before new Array or Object. 4 | func (e *Encoder) begin() { 5 | e.first = append(e.first, true) 6 | } 7 | 8 | // end should be called after Array or Object. 9 | func (e *Encoder) end() { 10 | if len(e.first) == 0 { 11 | return 12 | } 13 | e.first = e.first[:e.current()] 14 | } 15 | 16 | func (e *Encoder) current() int { return len(e.first) - 1 } 17 | 18 | // comma should be called before any new value. 19 | func (e *Encoder) comma() bool { 20 | // Writing commas. 21 | // 1. Before every field expect first. 22 | // 2. Before every array element except first. 23 | if len(e.first) == 0 { 24 | return false 25 | } 26 | current := e.current() 27 | _ = e.first[current] 28 | if e.first[current] { 29 | e.first[current] = false 30 | return false 31 | } 32 | return e.byte(',') || 33 | e.writeIndent() 34 | } 35 | -------------------------------------------------------------------------------- /enc_comma_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestEncoder_comma(t *testing.T) { 10 | t.Run("Array", func(t *testing.T) { 11 | var e Encoder 12 | e.ArrStart() 13 | e.Int(1) 14 | e.ArrStart() 15 | e.Int(2) 16 | e.RawStr(`3`) 17 | e.ArrEnd() 18 | e.ArrEnd() 19 | 20 | require.Equal(t, "[1,[2,3]]", e.String()) 21 | }) 22 | t.Run("Object", func(t *testing.T) { 23 | var e Encoder 24 | e.ObjStart() 25 | e.FieldStart("a") 26 | e.Int(1) 27 | e.FieldStart("b") 28 | e.Int(2) 29 | e.FieldStart("c") 30 | e.ArrStart() 31 | e.Int(1) 32 | e.Raw([]byte{'2'}) 33 | e.Float32(3.0) 34 | e.Float64(4.5) 35 | e.Num(Num{'2', '3'}) 36 | e.Bool(true) 37 | e.Bool(false) 38 | e.Null() 39 | e.Base64(Raw{1}) 40 | e.Bool(true) 41 | e.ArrEnd() 42 | e.ObjEnd() 43 | 44 | require.Equal(t, `{"a":1,"b":2,"c":[1,2,3,4.5,23,true,false,null,"AQ==",true]}`, e.String()) 45 | }) 46 | t.Run("NoPanic", func(t *testing.T) { 47 | var e Encoder 48 | e.ObjEnd() 49 | e.ObjEnd() 50 | e.ArrEnd() 51 | e.ArrEnd() 52 | }) 53 | } 54 | 55 | func BenchmarkEncoder_comma_overhead(b *testing.B) { 56 | // Measure overhead of ArrStart + comma + resetComma. 57 | // BenchmarkEncoder_comma-32 5.057 ns/op 0 B/op 0 allocs/op. 58 | var e Encoder 59 | b.ReportAllocs() 60 | for i := 0; i < b.N; i++ { 61 | e.ArrStart() 62 | e.comma() 63 | e.Reset() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /enc_float.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Float32 encodes float32. 4 | // 5 | // NB: Infinities and NaN are represented as null. 6 | func (e *Encoder) Float32(v float32) bool { 7 | return e.comma() || 8 | e.w.Float32(v) 9 | } 10 | 11 | // Float64 encodes float64. 12 | // 13 | // NB: Infinities and NaN are represented as null. 14 | func (e *Encoder) Float64(v float64) bool { 15 | return e.comma() || 16 | e.w.Float64(v) 17 | } 18 | -------------------------------------------------------------------------------- /enc_int.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Int encodes int. 4 | func (e *Encoder) Int(v int) bool { 5 | return e.comma() || 6 | e.w.Int(v) 7 | } 8 | 9 | // UInt encodes uint. 10 | func (e *Encoder) UInt(v uint) bool { 11 | return e.comma() || 12 | e.w.UInt(v) 13 | } 14 | 15 | // UInt8 encodes uint8. 16 | func (e *Encoder) UInt8(v uint8) bool { 17 | return e.comma() || 18 | e.w.UInt8(v) 19 | } 20 | 21 | // Int8 encodes int8. 22 | func (e *Encoder) Int8(v int8) bool { 23 | return e.comma() || 24 | e.w.Int8(v) 25 | } 26 | -------------------------------------------------------------------------------- /enc_int_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestEncoder_Uint64(t *testing.T) { 13 | test := func(i uint64) (string, func(t *testing.T)) { 14 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 15 | enc := GetEncoder() 16 | enc.Reset() 17 | enc.UInt64(i) 18 | require.Equal(t, enc.String(), strconv.FormatUint(i, 10)) 19 | } 20 | } 21 | overflows := func(i uint64) bool { 22 | result := i * 10 23 | return result/10 != i 24 | } 25 | 26 | t.Run(test(0)) 27 | t.Run(test(1337)) 28 | t.Run(test(math.MaxUint64)) 29 | for i := uint64(1); !overflows(i); i *= 10 { 30 | t.Run(test(i)) 31 | t.Run(test(i + 1)) 32 | } 33 | } 34 | 35 | func TestEncoder_Uint32(t *testing.T) { 36 | test := func(i uint32) (string, func(t *testing.T)) { 37 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 38 | enc := GetEncoder() 39 | enc.Reset() 40 | enc.UInt32(i) 41 | require.Equal(t, enc.String(), strconv.FormatUint(uint64(i), 10)) 42 | } 43 | } 44 | overflows := func(i uint32) bool { 45 | result := i * 10 46 | return result/10 != i 47 | } 48 | 49 | t.Run(test(0)) 50 | t.Run(test(1337)) 51 | t.Run(test(math.MaxUint32)) 52 | for i := uint32(1); !overflows(i); i *= 10 { 53 | t.Run(test(i)) 54 | t.Run(test(i + 1)) 55 | } 56 | } 57 | 58 | func TestEncoder_Uint16(t *testing.T) { 59 | test := func(i uint16) (string, func(t *testing.T)) { 60 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 61 | enc := GetEncoder() 62 | enc.Reset() 63 | enc.UInt16(i) 64 | require.Equal(t, enc.String(), strconv.FormatUint(uint64(i), 10)) 65 | } 66 | } 67 | overflows := func(i uint16) bool { 68 | result := i * 10 69 | return result/10 != i 70 | } 71 | 72 | t.Run(test(0)) 73 | t.Run(test(1337)) 74 | t.Run(test(math.MaxUint16)) 75 | for i := uint16(1); !overflows(i); i *= 10 { 76 | t.Run(test(i)) 77 | t.Run(test(i + 1)) 78 | } 79 | } 80 | 81 | func TestEncoder_Uint8(t *testing.T) { 82 | test := func(i uint8) (string, func(t *testing.T)) { 83 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 84 | enc := GetEncoder() 85 | enc.Reset() 86 | enc.UInt8(i) 87 | require.Equal(t, enc.String(), strconv.FormatUint(uint64(i), 10)) 88 | } 89 | } 90 | overflows := func(i uint8) bool { 91 | result := i * 10 92 | return result/10 != i 93 | } 94 | 95 | t.Run(test(0)) 96 | t.Run(test(237)) 97 | t.Run(test(math.MaxUint8)) 98 | for i := uint8(1); !overflows(i); i *= 10 { 99 | t.Run(test(i)) 100 | t.Run(test(i + 1)) 101 | } 102 | } 103 | 104 | func TestEncoder_Int16(t *testing.T) { 105 | test := func(i int16) (string, func(t *testing.T)) { 106 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 107 | enc := GetEncoder() 108 | enc.Reset() 109 | enc.Int16(i) 110 | require.Equal(t, enc.String(), strconv.FormatInt(int64(i), 10)) 111 | } 112 | } 113 | overflows := func(i int16) bool { 114 | result := i * 10 115 | return result/10 != i 116 | } 117 | 118 | t.Run(test(0)) 119 | t.Run(test(-13)) 120 | t.Run(test(math.MaxInt16)) 121 | for i := int16(1); !overflows(i); i *= 10 { 122 | t.Run(test(i)) 123 | t.Run(test(i + 1)) 124 | } 125 | } 126 | 127 | func TestEncoder_Int8(t *testing.T) { 128 | test := func(i int8) (string, func(t *testing.T)) { 129 | return fmt.Sprintf("Test%d", i), func(t *testing.T) { 130 | enc := GetEncoder() 131 | enc.Reset() 132 | enc.Int8(i) 133 | require.Equal(t, enc.String(), strconv.FormatInt(int64(i), 10)) 134 | } 135 | } 136 | overflows := func(i int8) bool { 137 | result := i * 10 138 | return result/10 != i 139 | } 140 | 141 | t.Run(test(0)) 142 | t.Run(test(-13)) 143 | t.Run(test(math.MaxInt8)) 144 | for i := int8(1); !overflows(i); i *= 10 { 145 | t.Run(test(i)) 146 | t.Run(test(i + 1)) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /enc_num.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Num encodes number. 4 | func (e *Encoder) Num(v Num) bool { 5 | return e.comma() || 6 | e.w.Num(v) 7 | } 8 | -------------------------------------------------------------------------------- /enc_str.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Str encodes string without html escaping. 4 | // 5 | // Use StrEscape to escape html, this is default for encoding/json and 6 | // should be used by default for untrusted strings. 7 | func (e *Encoder) Str(v string) bool { 8 | return e.comma() || 9 | e.w.Str(v) 10 | } 11 | 12 | // ByteStr encodes byte slice without html escaping. 13 | // 14 | // Use ByteStrEscape to escape html, this is default for encoding/json and 15 | // should be used by default for untrusted strings. 16 | func (e *Encoder) ByteStr(v []byte) bool { 17 | return e.comma() || 18 | e.w.ByteStr(v) 19 | } 20 | -------------------------------------------------------------------------------- /enc_str_escape.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // StrEscape encodes string with html special characters escaping. 4 | func (e *Encoder) StrEscape(v string) bool { 5 | return e.comma() || 6 | e.w.StrEscape(v) 7 | } 8 | 9 | // ByteStrEscape encodes string with html special characters escaping. 10 | func (e *Encoder) ByteStrEscape(v []byte) bool { 11 | return e.comma() || 12 | e.w.ByteStrEscape(v) 13 | } 14 | -------------------------------------------------------------------------------- /enc_str_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestEncoder_Str(t *testing.T) { 14 | testCases := []struct { 15 | input string 16 | }{ 17 | {``}, 18 | {`abcd`}, 19 | { 20 | `abcd\nH\tel\tl\ro\\World\r` + "\n\rHello\r\tHi", 21 | }, 22 | {"\x00"}, 23 | {"\x00 "}, 24 | {`"hello, world!"`}, 25 | 26 | {strings.Repeat("a", encoderBufSize)}, 27 | } 28 | for i, tt := range testCases { 29 | tt := tt 30 | t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { 31 | for _, enc := range []struct { 32 | name string 33 | enc func(e *Encoder, input string) bool 34 | }{ 35 | {"Str", (*Encoder).Str}, 36 | {"Bytes", func(e *Encoder, input string) bool { 37 | return e.ByteStr([]byte(tt.input)) 38 | }}, 39 | } { 40 | enc := enc 41 | t.Run(enc.name, func(t *testing.T) { 42 | requireCompat(t, func(e *Encoder) { 43 | enc.enc(e, tt.input) 44 | }, tt.input) 45 | 46 | t.Run("Decode", func(t *testing.T) { 47 | e := GetEncoder() 48 | enc.enc(e, tt.input) 49 | 50 | i := GetDecoder() 51 | i.ResetBytes(e.Bytes()) 52 | s, err := i.Str() 53 | require.NoError(t, err) 54 | require.Equal(t, tt.input, s) 55 | }) 56 | }) 57 | } 58 | }) 59 | } 60 | t.Run("Quotes", func(t *testing.T) { 61 | const ( 62 | v = "\"/\"" 63 | ) 64 | requireCompat(t, func(e *Encoder) { 65 | e.StrEscape(v) 66 | }, v) 67 | }) 68 | t.Run("QuotesObj", func(t *testing.T) { 69 | const ( 70 | k = "k" 71 | v = "\"/\"" 72 | ) 73 | 74 | cb := func(e *Encoder) { 75 | e.ObjStart() 76 | e.FieldStart(k) 77 | e.Str(v) 78 | e.ObjEnd() 79 | t.Log(e) 80 | } 81 | 82 | var e Encoder 83 | cb(&e) 84 | 85 | var target map[string]string 86 | require.NoError(t, json.Unmarshal(e.Bytes(), &target)) 87 | assert.Equal(t, v, target[k]) 88 | requireCompat(t, cb, map[string]string{k: v}) 89 | }) 90 | } 91 | 92 | func TestEncoder_StrEscape(t *testing.T) { 93 | testCases := []struct { 94 | input, expect string 95 | }{ 96 | {"Foo", `"Foo"`}, 97 | {"\uFFFD", `"�"`}, 98 | {"a\xc5z", `"a\ufffdz"`}, 99 | {"Hello\\\n\r\\` + "\n\rW\torld\u2028", 102 | `"\u003chtml\u003eHello\\\\\\n\\r\\\\\n\rW\torld\u2028\u003c/html\u003e"`, 103 | }, 104 | } 105 | for i, tt := range testCases { 106 | tt := tt 107 | t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { 108 | for _, enc := range []struct { 109 | name string 110 | enc func(e *Encoder, input string) bool 111 | }{ 112 | {"Str", (*Encoder).StrEscape}, 113 | {"Bytes", func(e *Encoder, input string) bool { 114 | return e.ByteStrEscape([]byte(tt.input)) 115 | }}, 116 | } { 117 | enc := enc 118 | t.Run(enc.name, func(t *testing.T) { 119 | requireCompat(t, func(e *Encoder) { 120 | enc.enc(e, tt.input) 121 | }, tt.input) 122 | }) 123 | } 124 | }) 125 | } 126 | t.Run("QuotesEscape", func(t *testing.T) { 127 | const ( 128 | v = "\"/\"" 129 | ) 130 | requireCompat(t, func(e *Encoder) { 131 | e.StrEscape(v) 132 | }, v) 133 | }) 134 | } 135 | -------------------------------------------------------------------------------- /enc_stream.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import "io" 4 | 5 | const ( 6 | encoderBufSize = 512 7 | minEncoderBufSize = 32 8 | ) 9 | 10 | // NewStreamingEncoder creates new streaming encoder. 11 | func NewStreamingEncoder(w io.Writer, bufSize int) *Encoder { 12 | switch { 13 | case bufSize < 0: 14 | bufSize = encoderBufSize 15 | case bufSize < minEncoderBufSize: 16 | bufSize = minEncoderBufSize 17 | } 18 | return &Encoder{ 19 | w: Writer{ 20 | Buf: make([]byte, 0, bufSize), 21 | stream: newStreamState(w), 22 | }, 23 | } 24 | } 25 | 26 | // Close flushes underlying buffer to writer in streaming mode. 27 | // Otherwise, it does nothing. 28 | func (e *Encoder) Close() error { 29 | return e.w.Close() 30 | } 31 | -------------------------------------------------------------------------------- /enc_stream_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/go-faster/errors" 12 | ) 13 | 14 | func TestEncoderStreamingCheck(t *testing.T) { 15 | a := require.New(t) 16 | 17 | e := NewStreamingEncoder(io.Discard, -1) 18 | 19 | _, err := e.Write([]byte("hello")) 20 | a.ErrorIs(err, errStreaming) 21 | 22 | _, err = e.WriteTo(io.Discard) 23 | a.ErrorIs(err, errStreaming) 24 | 25 | a.PanicsWithError(errStreaming.Error(), func() { 26 | _ = e.String() 27 | }) 28 | } 29 | 30 | type errWriter struct { 31 | err error 32 | n int 33 | } 34 | 35 | func (e *errWriter) Write(p []byte) (int, error) { 36 | n := e.n 37 | if n <= 0 { 38 | n = len(p) 39 | } 40 | return n, e.err 41 | } 42 | 43 | func TestEncoder_Close(t *testing.T) { 44 | errTest := errors.New("test") 45 | 46 | t.Run("FlushErr", func(t *testing.T) { 47 | ew := &errWriter{err: errTest} 48 | e := NewStreamingEncoder(ew, -1) 49 | e.Null() 50 | 51 | require.ErrorIs(t, e.Close(), errTest) 52 | }) 53 | t.Run("WriteErr", func(t *testing.T) { 54 | ew := &errWriter{err: errTest} 55 | e := NewStreamingEncoder(ew, minEncoderBufSize) 56 | e.Obj(func(e *Encoder) { 57 | e.FieldStart(strings.Repeat("a", minEncoderBufSize)) 58 | e.Null() 59 | }) 60 | 61 | require.ErrorIs(t, e.Close(), errTest) 62 | }) 63 | t.Run("ShortWrite", func(t *testing.T) { 64 | ew := &errWriter{n: 1} 65 | e := NewStreamingEncoder(ew, -1) 66 | e.Null() 67 | 68 | require.ErrorIs(t, e.Close(), io.ErrShortWrite) 69 | }) 70 | t.Run("OK", func(t *testing.T) { 71 | e := NewStreamingEncoder(io.Discard, -1) 72 | e.Null() 73 | 74 | require.NoError(t, e.Close()) 75 | }) 76 | t.Run("NoStreaming", func(t *testing.T) { 77 | var e Encoder 78 | e.Null() 79 | 80 | require.NoError(t, e.Close()) 81 | }) 82 | } 83 | 84 | func TestEncoder_ResetWriter(t *testing.T) { 85 | do := func(e *Encoder) { 86 | e.ObjStart() 87 | e.FieldStart(strings.Repeat("a", minEncoderBufSize)) 88 | e.Null() 89 | e.ObjEnd() 90 | 91 | require.NoError(t, e.Close()) 92 | } 93 | 94 | var e Encoder 95 | do(&e) 96 | expected := e.String() 97 | 98 | for range [3]struct{}{} { 99 | var got strings.Builder 100 | e.ResetWriter(&got) 101 | do(&e) 102 | require.Equal(t, expected, got.String()) 103 | } 104 | } 105 | 106 | // This benchmark is used to measure the overhead of ignoring errors. 107 | func BenchmarkSkipError(b *testing.B) { 108 | e := NewStreamingEncoder(io.Discard, minEncoderBufSize) 109 | e.w.stream.setError(errors.New("test")) 110 | 111 | b.ResetTimer() 112 | b.ReportAllocs() 113 | 114 | for i := 0; i < b.N; i++ { 115 | encodeObject(e) 116 | } 117 | } 118 | 119 | func TestStreamingEncoderBufferSize(t *testing.T) { 120 | e := NewStreamingEncoder(io.Discard, -1) 121 | assert.Equal(t, encoderBufSize, cap(e.w.Buf)) 122 | e = NewStreamingEncoder(io.Discard, minEncoderBufSize-1) 123 | assert.Equal(t, minEncoderBufSize, cap(e.w.Buf)) 124 | } 125 | -------------------------------------------------------------------------------- /float_bench_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkFloat(b *testing.B) { 9 | data := []byte(`1.1`) 10 | b.Run("Std", func(b *testing.B) { 11 | b.ReportAllocs() 12 | for n := 0; n < b.N; n++ { 13 | var result float64 14 | if err := json.Unmarshal([]byte(`1.1`), &result); err != nil { 15 | b.Fatal(err) 16 | } 17 | } 18 | }) 19 | b.Run("JX", func(b *testing.B) { 20 | b.ReportAllocs() 21 | d := GetDecoder() 22 | for n := 0; n < b.N; n++ { 23 | d.ResetBytes(data) 24 | if _, err := d.Float64(); err != nil { 25 | b.Fatal(err) 26 | } 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | //go:generate go run ./tools/mkint 4 | -------------------------------------------------------------------------------- /go.coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | go test -race -v -coverpkg=./... -coverprofile=profile.out ./... 6 | go tool cover -func profile.out 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-faster/jx 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/go-faster/errors v0.7.1 7 | github.com/segmentio/asm v1.2.0 8 | github.com/stretchr/testify v1.10.0 9 | golang.org/x/exp v0.0.0-20230116083435-1de6713980de 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/kr/pretty v0.2.1 // indirect 15 | github.com/kr/text v0.2.0 // indirect 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | golang.org/x/sys v0.1.0 // indirect 18 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= 5 | github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= 6 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 7 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 8 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 9 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 10 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 11 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 15 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 16 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 17 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 18 | golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0= 19 | golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 20 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 21 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 24 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 26 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 27 | -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | echo "test" 6 | go test --timeout 5m ./... 7 | 8 | echo "test purego" 9 | go test --timeout 5m -tags purego ./... 10 | 11 | echo "test -race" 12 | go test --timeout 5m -race ./... 13 | -------------------------------------------------------------------------------- /int_bench_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkEncoder_Int(b *testing.B) { 11 | const v = 0xffffff 12 | b.Run("Strconv", func(b *testing.B) { 13 | b.ReportAllocs() 14 | var buf []byte 15 | for i := 0; i < b.N; i++ { 16 | buf = buf[:0] 17 | buf = strconv.AppendInt(buf, v, 10) 18 | } 19 | }) 20 | b.Run("Std", func(b *testing.B) { 21 | b.ReportAllocs() 22 | buf := new(bytes.Buffer) 23 | e := json.NewEncoder(buf) 24 | for i := 0; i < b.N; i++ { 25 | buf.Reset() 26 | if err := e.Encode(v); err != nil { 27 | b.Fatal(err) 28 | } 29 | } 30 | }) 31 | b.Run("JX", func(b *testing.B) { 32 | b.ReportAllocs() 33 | e := GetEncoder() 34 | for i := 0; i < b.N; i++ { 35 | e.Reset() 36 | e.UInt64(v) 37 | } 38 | }) 39 | } 40 | 41 | func BenchmarkDecoder_Int64(b *testing.B) { 42 | input := []byte(`100`) 43 | b.Run("Std", func(b *testing.B) { 44 | b.ReportAllocs() 45 | for i := 0; i < b.N; i++ { 46 | result := int64(0) 47 | if err := json.Unmarshal(input, &result); err != nil { 48 | b.Fatal(err) 49 | } 50 | } 51 | }) 52 | b.Run("JX", func(b *testing.B) { 53 | b.ReportAllocs() 54 | d := GetDecoder() 55 | for i := 0; i < b.N; i++ { 56 | d.ResetBytes(input) 57 | if _, err := d.Int64(); err != nil { 58 | b.Fatal(err) 59 | } 60 | } 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /internal/byteseq/byteseq.go: -------------------------------------------------------------------------------- 1 | // Package byteseq provides a Byteseq type that can be used to represent a sequence of bytes. 2 | package byteseq 3 | 4 | import "unicode/utf8" 5 | 6 | // Byteseq is common interface for byte slices and strings. 7 | type Byteseq interface { 8 | string | []byte 9 | } 10 | 11 | // DecodeRuneInByteseq decodes the first UTF-8 encoded rune in val and returns the rune and its size in bytes. 12 | func DecodeRuneInByteseq[T Byteseq](val T) (r rune, size int) { 13 | var tmp [4]byte 14 | n := copy(tmp[:], val) 15 | return utf8.DecodeRune(tmp[:n]) 16 | } 17 | -------------------------------------------------------------------------------- /internal/byteseq/byteseq_test.go: -------------------------------------------------------------------------------- 1 | package byteseq 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "unicode/utf8" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestDecodeRuneInByteseq(t *testing.T) { 12 | for i, tt := range []struct { 13 | s string 14 | }{ 15 | {""}, 16 | {"\x00"}, 17 | {"a"}, 18 | {"ж"}, 19 | {"🤡"}, 20 | {"👩🏿"}, 21 | } { 22 | tt := tt 23 | t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) { 24 | gotR, gotSize := DecodeRuneInByteseq(tt.s) 25 | expectR, expectSize := utf8.DecodeRuneInString(tt.s) 26 | 27 | require.Equal(t, expectR, gotR) 28 | require.Equal(t, expectSize, gotSize) 29 | }) 30 | } 31 | } 32 | 33 | func BenchmarkDecodeRuneInByteseq(b *testing.B) { 34 | var ( 35 | buf [4]byte 36 | result rune 37 | ) 38 | utf8.EncodeRune(buf[:], 'ж') 39 | 40 | b.ReportAllocs() 41 | b.ResetTimer() 42 | 43 | for i := 0; i < b.N; i++ { 44 | result, _ = DecodeRuneInByteseq(buf[:]) 45 | } 46 | 47 | if result != 'ж' { 48 | b.Fatal(result) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /invalid_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestInvalidFloat(t *testing.T) { 10 | inputs := []string{ 11 | `1.e1`, // dot without following digit 12 | `1.`, // dot can not be the last char 13 | ``, // empty number 14 | `01`, // extra leading zero 15 | `-`, // negative without digit 16 | `--`, // double negative 17 | `--2`, // double negative 18 | } 19 | for _, input := range inputs { 20 | t.Run(input, func(t *testing.T) { 21 | should := require.New(t) 22 | iter := DecodeStr(input + ",") 23 | should.Error(iter.Skip()) 24 | iter = DecodeStr(input + ",") 25 | _, err := iter.Float64() 26 | should.Error(err) 27 | iter = DecodeStr(input + ",") 28 | _, err = iter.Float32() 29 | should.Error(err) 30 | }) 31 | } 32 | } 33 | 34 | func TestValidPositive(t *testing.T) { 35 | for _, tt := range []struct { 36 | Name string 37 | Value string 38 | }{ 39 | // https://github.com/json-iterator/go/issues/520 40 | {Name: "Number", Value: "1"}, 41 | 42 | {Name: "BlankObj", Value: "{}"}, 43 | {Name: "BlankArr", Value: "[]"}, 44 | {Name: "Example", Value: `{"menu": { 45 | "id": "file", 46 | "value": "File", 47 | "popup": { 48 | "menuitem": [ 49 | {"value": "New", "onclick": "CreateNewDoc()"}, 50 | {"value": "Open", "onclick": "OpenDoc()"}, 51 | {"value": "Close", "onclick": "CloseDoc()"} 52 | ] 53 | } 54 | }}`}, 55 | } { 56 | t.Run(tt.Name, func(t *testing.T) { 57 | require.True(t, Valid([]byte(tt.Value)), "should be valid") 58 | }) 59 | } 60 | } 61 | 62 | func TestValidNegative(t *testing.T) { 63 | for _, tt := range []struct { 64 | Name string 65 | Value string 66 | }{ 67 | {Name: "Blank", Value: ""}, 68 | {Name: "InvalidCharacters", Value: "foo"}, 69 | {Name: "NotClosed", Value: "{"}, 70 | {Name: "NotClosed2", Value: "{{}"}, 71 | {Name: "NotOpened", Value: "{}}"}, 72 | {Name: "NotOpenedArr", Value: "{[}]"}, 73 | {Name: "NotOpenedArr2", Value: "{}]"}, 74 | } { 75 | t.Run(tt.Name, func(t *testing.T) { 76 | require.False(t, Valid([]byte(tt.Value)), "should be invalid") 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /jx.go: -------------------------------------------------------------------------------- 1 | // Package jx implements RFC 7159 json encoding and decoding. 2 | package jx 3 | 4 | import ( 5 | "sync" 6 | ) 7 | 8 | // Valid reports whether data is valid json. 9 | func Valid(data []byte) bool { 10 | d := GetDecoder() 11 | defer PutDecoder(d) 12 | d.ResetBytes(data) 13 | return d.Validate() == nil 14 | } 15 | 16 | var ( 17 | encPool = &sync.Pool{ 18 | New: func() interface{} { 19 | return &Encoder{} 20 | }, 21 | } 22 | writerPool = &sync.Pool{ 23 | New: func() interface{} { 24 | return &Writer{} 25 | }, 26 | } 27 | decPool = &sync.Pool{ 28 | New: func() interface{} { 29 | return &Decoder{} 30 | }, 31 | } 32 | ) 33 | 34 | // GetDecoder gets *Decoder from pool. 35 | func GetDecoder() *Decoder { 36 | return decPool.Get().(*Decoder) 37 | } 38 | 39 | // PutDecoder puts *Decoder into pool. 40 | func PutDecoder(d *Decoder) { 41 | d.Reset(nil) 42 | decPool.Put(d) 43 | } 44 | 45 | // GetEncoder returns *Encoder from pool. 46 | func GetEncoder() *Encoder { 47 | return encPool.Get().(*Encoder) 48 | } 49 | 50 | // PutEncoder puts *Encoder to pool 51 | func PutEncoder(e *Encoder) { 52 | e.Reset() 53 | e.SetIdent(0) 54 | encPool.Put(e) 55 | } 56 | 57 | // GetWriter returns *Writer from pool. 58 | func GetWriter() *Writer { 59 | return writerPool.Get().(*Writer) 60 | } 61 | 62 | // PutWriter puts *Writer to pool 63 | func PutWriter(e *Writer) { 64 | e.Reset() 65 | writerPool.Put(e) 66 | } 67 | -------------------------------------------------------------------------------- /jx_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPutEncoder(t *testing.T) { 12 | var wg sync.WaitGroup 13 | for j := 0; j < 4; j++ { 14 | wg.Add(1) 15 | go func() { 16 | defer wg.Done() 17 | for i := 0; i < 1024; i++ { 18 | e := GetEncoder() 19 | e.RawStr("false") 20 | assert.Equal(t, "false", e.String()) 21 | PutEncoder(e) 22 | } 23 | }() 24 | } 25 | wg.Wait() 26 | } 27 | 28 | func TestPutDecoder(t *testing.T) { 29 | var wg sync.WaitGroup 30 | for j := 0; j < 4; j++ { 31 | wg.Add(1) 32 | go func() { 33 | defer wg.Done() 34 | for i := 0; i < 1024; i++ { 35 | d := GetDecoder() 36 | assert.Equal(t, d.Next(), Invalid) 37 | d.Reset(bytes.NewBufferString("false")) 38 | assert.Equal(t, d.Next(), Bool) 39 | v, err := d.Bool() 40 | assert.NoError(t, err) 41 | assert.Equal(t, d.Next(), Invalid) 42 | assert.False(t, v) 43 | PutDecoder(d) 44 | } 45 | }() 46 | } 47 | wg.Wait() 48 | } 49 | -------------------------------------------------------------------------------- /null_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestWriteNull(t *testing.T) { 10 | testEncoderModes(t, func(e *Encoder) { 11 | e.Null() 12 | }, "null") 13 | } 14 | 15 | func TestDecodeNullArrayElement(t *testing.T) { 16 | should := require.New(t) 17 | iter := DecodeStr(`[null,"a"]`) 18 | should.True(iter.Elem()) 19 | should.NoError(iter.Null()) 20 | should.True(iter.Elem()) 21 | s, err := iter.Str() 22 | should.NoError(err) 23 | should.Equal("a", s) 24 | } 25 | 26 | func TestDecodeNullString(t *testing.T) { 27 | should := require.New(t) 28 | iter := DecodeStr(`[null,"a"]`) 29 | should.True(iter.Elem()) 30 | should.NoError(iter.Null()) 31 | should.True(iter.Elem()) 32 | s, err := iter.Str() 33 | should.NoError(err) 34 | should.Equal("a", s) 35 | } 36 | 37 | func TestDecodeNullSkip(t *testing.T) { 38 | iter := DecodeStr(`[null,"a"]`) 39 | iter.Elem() 40 | iter.Skip() 41 | iter.Elem() 42 | if s, _ := iter.Str(); s != "a" { 43 | t.FailNow() 44 | } 45 | } 46 | 47 | func TestNullError(t *testing.T) { 48 | a := require.New(t) 49 | var ( 50 | b = [4]byte{'n', 'u', 'l', 'l'} 51 | valid = b 52 | ) 53 | for i := range b { 54 | // Reset buffer. 55 | b = valid 56 | for c := byte(0); c < 255; c++ { 57 | // Skip expected value. 58 | if valid[i] == c { 59 | continue 60 | } 61 | // Skip space as first character. 62 | if i == 0 && spaceSet[c] == 1 { 63 | continue 64 | } 65 | b[i] = c 66 | var token *badTokenErr 67 | a.ErrorAs(DecodeBytes(b[:]).Null(), &token) 68 | a.Equalf(c, token.Token, "%c != %c (%q)", c, token.Token, b) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /obj_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestEmptyObject(t *testing.T) { 13 | iter := DecodeStr(`{}`) 14 | require.NoError(t, iter.Obj(func(iter *Decoder, field string) error { 15 | t.Error("should not call") 16 | return nil 17 | })) 18 | } 19 | 20 | func TestOneField(t *testing.T) { 21 | should := require.New(t) 22 | d := DecodeStr(`{"a": "stream"}`) 23 | should.NoError(d.Obj(func(iter *Decoder, field string) error { 24 | should.Equal("a", field) 25 | return iter.Skip() 26 | })) 27 | } 28 | 29 | func TestEncoder_SetIdent(t *testing.T) { 30 | should := require.New(t) 31 | e := GetEncoder() 32 | e.SetIdent(2) 33 | e.ObjStart() 34 | e.FieldStart("hello") 35 | e.Int(1) 36 | e.FieldStart("world") 37 | e.Int(2) 38 | e.FieldStart("obj") 39 | e.ObjStart() 40 | e.FieldStart("a") 41 | e.Str("b") 42 | e.ObjEnd() 43 | e.FieldStart("data") 44 | e.ArrStart() 45 | e.Int(1) 46 | e.Int(2) 47 | e.ArrEnd() 48 | e.ObjEnd() 49 | expected := `{ 50 | "hello": 1, 51 | "world": 2, 52 | "obj": { 53 | "a": "b" 54 | }, 55 | "data": [ 56 | 1, 57 | 2 58 | ] 59 | }` 60 | should.Equal(expected, e.String()) 61 | 62 | t.Run("Std", func(t *testing.T) { 63 | b := new(bytes.Buffer) 64 | enc := json.NewEncoder(b) 65 | enc.SetIndent("", " ") 66 | require.NoError(t, enc.Encode(struct { 67 | Hello int `json:"hello"` 68 | World int `json:"world"` 69 | Obj struct { 70 | A string `json:"a"` 71 | } `json:"obj"` 72 | Data []int `json:"data"` 73 | }{ 74 | Hello: 1, 75 | World: 2, 76 | Obj: struct { 77 | A string `json:"a"` 78 | }{A: "b"}, 79 | Data: []int{1, 2}, 80 | })) 81 | 82 | // Remove trialing newline from expected. 83 | exp := b.String() 84 | exp = strings.TrimRight(exp, "\n") 85 | 86 | require.Equal(t, exp, e.String()) 87 | }) 88 | } 89 | 90 | func TestDecoder_Obj(t *testing.T) { 91 | // https://github.com/json-iterator/go/issues/549 92 | b := []byte(`{"\u6D88\u606F":"\u6D88\u606F"}`) 93 | 94 | v := struct { 95 | Message string `json:"消息"` 96 | }{} 97 | require.NoError(t, json.Unmarshal(b, &v)) 98 | require.Equal(t, "消息", v.Message) 99 | 100 | var gotKey, gotVal string 101 | require.NoError(t, DecodeBytes(b).Obj(func(d *Decoder, key string) error { 102 | str, err := d.Str() 103 | if err != nil { 104 | return err 105 | } 106 | gotKey = key 107 | gotVal = str 108 | return nil 109 | })) 110 | 111 | require.Equal(t, v.Message, gotVal) 112 | require.Equal(t, v.Message, gotKey) 113 | } 114 | -------------------------------------------------------------------------------- /pipe_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPipe(t *testing.T) { 12 | // Test that Encoder and Decoder can communicate via pipe. 13 | r, w := io.Pipe() 14 | const objects = 1024 * 10 15 | done := make(chan struct{}) 16 | go func() { 17 | defer close(done) 18 | defer func() { _ = w.CloseWithError(io.EOF) }() 19 | e := GetEncoder() 20 | // Write objects to w. 21 | for i := 0; i < objects; i++ { 22 | e.Reset() 23 | e.ObjEmpty() 24 | if _, err := e.WriteTo(w); err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | } 29 | }() 30 | 31 | d := GetDecoder() 32 | d.Reset(r) 33 | // Read exact count of objects. 34 | for i := 0; i < objects; i++ { 35 | if err := d.Obj(nil); err != nil { 36 | t.Error(err) 37 | break 38 | } 39 | } 40 | 41 | // Assert correct end of pipe. 42 | assert.ErrorIs(t, d.Skip(), io.EOF, "unexpected read") 43 | assert.Equal(t, d.Next(), Invalid) 44 | 45 | // Wait for encoder to finish. 46 | select { 47 | case <-done: 48 | case <-time.After(time.Second): 49 | t.Error("timeout") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /suite_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "encoding/json" 5 | "path" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestSuite(t *testing.T) { 14 | // https://github.com/nst/JSONTestSuite 15 | // By Nicolas Seriot (https://github.com/nst) 16 | dir := path.Join("testdata", "test_parsing") 17 | files, err := testdata.ReadDir(dir) 18 | require.NoError(t, err) 19 | 20 | const ( 21 | Accept = "y_" 22 | Reject = "n_" 23 | Undefined = "i_" 24 | ) 25 | for _, f := range files { 26 | if f.IsDir() || !strings.HasSuffix(f.Name(), ".json") { 27 | continue 28 | } 29 | 30 | name := strings.TrimSuffix(f.Name(), ".json") 31 | action := f.Name()[:2] 32 | 33 | file := path.Join(dir, f.Name()) 34 | data, err := testdata.ReadFile(file) 35 | require.NoError(t, err) 36 | 37 | t.Run(name, func(t *testing.T) { 38 | r := GetDecoder() 39 | r.ResetBytes(data) 40 | defer PutDecoder(r) 41 | 42 | _, decodeErr := r.Any() 43 | switch action { 44 | case Accept: 45 | assert.True(t, Valid(data), "validate") 46 | assert.True(t, json.Valid(data), "std") 47 | assert.NoError(t, decodeErr, "%#v", string(data)) 48 | case Reject: 49 | assert.False(t, Valid(data), "validate") 50 | assert.False(t, json.Valid(data), "std") 51 | // TODO: assert decodeErr + buffer drain? 52 | case Undefined: 53 | if decodeErr == nil { 54 | t.Log("Accept") 55 | } else { 56 | t.Logf("Reject: %v", decodeErr) 57 | } 58 | default: 59 | t.Fatalf("Unknown prefix %q", action) 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testdata/bools.json: -------------------------------------------------------------------------------- 1 | [ 2 | true, 3 | false, 4 | true, 5 | false, 6 | true, 7 | false, 8 | true, 9 | false, 10 | true, 11 | false, 12 | true, 13 | false, 14 | true, 15 | false, 16 | true, 17 | false, 18 | true, 19 | false, 20 | true, 21 | false, 22 | true, 23 | false, 24 | true, 25 | false, 26 | true, 27 | false, 28 | true, 29 | false, 30 | true, 31 | false, 32 | true, 33 | false, 34 | true, 35 | false, 36 | true, 37 | false, 38 | true, 39 | false, 40 | true, 41 | false, 42 | true, 43 | false, 44 | true, 45 | false, 46 | true, 47 | false, 48 | true, 49 | false, 50 | true, 51 | false, 52 | true, 53 | false, 54 | true, 55 | false, 56 | true, 57 | false, 58 | true, 59 | false, 60 | true, 61 | false, 62 | true, 63 | false, 64 | true, 65 | false, 66 | true, 67 | false, 68 | true, 69 | false, 70 | true, 71 | false, 72 | true, 73 | false, 74 | true, 75 | false, 76 | true, 77 | false, 78 | true, 79 | false, 80 | true, 81 | false, 82 | true, 83 | false, 84 | true, 85 | false, 86 | true, 87 | false, 88 | true, 89 | false, 90 | true, 91 | false, 92 | true, 93 | false, 94 | true, 95 | false, 96 | true, 97 | false, 98 | true, 99 | false, 100 | true, 101 | false 102 | ] 103 | 104 | -------------------------------------------------------------------------------- /testdata/floats.json: -------------------------------------------------------------------------------- 1 | [ 2 | 0.6, 3 | 0.9, 4 | 0.6, 5 | 0.4, 6 | 0.4, 7 | 0.6, 8 | 0.0, 9 | 0.1, 10 | 0.0, 11 | 0.3, 12 | 0.5, 13 | 0.8, 14 | 0.2, 15 | 0.3, 16 | 0.3, 17 | 0.4, 18 | 0.2, 19 | 0.2, 20 | 0.6, 21 | 0.2, 22 | 0.2, 23 | 0.3, 24 | 0.5, 25 | 0.8, 26 | 0.2, 27 | 0.2, 28 | 0.7, 29 | 0.2, 30 | 0.8, 31 | 0.6, 32 | 0.5, 33 | 0.0, 34 | 0.1, 35 | 0.6, 36 | 0.9, 37 | 0.0, 38 | 0.5, 39 | 0.0, 40 | 0.6, 41 | 0.3, 42 | 0.1, 43 | 0.5, 44 | 0.5, 45 | 0.2, 46 | 0.4, 47 | 0.5, 48 | 0.2, 49 | 0.2, 50 | 0.7, 51 | 0.3, 52 | 0.8, 53 | 0.2, 54 | 0.8, 55 | 0.0, 56 | 0.9, 57 | 0.0, 58 | 0.2, 59 | 0.6, 60 | 0.2, 61 | 0.3, 62 | 0.9, 63 | 0.7, 64 | 0.8, 65 | 0.7, 66 | 0.1, 67 | 0.4, 68 | 0.8, 69 | 0.6, 70 | 0.9, 71 | 0.9, 72 | 0.0, 73 | 0.4, 74 | 0.9, 75 | 0.9, 76 | 0.3, 77 | 0.6, 78 | 0.7, 79 | 0.5, 80 | 0.6, 81 | 0.5, 82 | 0.7, 83 | 0.4, 84 | 0.1, 85 | 0.9, 86 | 0.8, 87 | 0.3, 88 | 0.7, 89 | 0.6, 90 | 0.0, 91 | 0.6, 92 | 0.6, 93 | 0.3, 94 | 0.2, 95 | 0.5, 96 | 0.1, 97 | 0.2, 98 | 0.6, 99 | 0.1, 100 | 0.2, 101 | 0.4 102 | ] 103 | -------------------------------------------------------------------------------- /testdata/integers.json: -------------------------------------------------------------------------------- 1 | [ 2 | 604660287, 3 | 940509088, 4 | 664560053, 5 | 437714187, 6 | 424637497, 7 | 686823072, 8 | 656370192, 9 | 156519254, 10 | 969695189, 11 | 300911860, 12 | 515212628, 13 | 813639960, 14 | 214263872, 15 | 380657189, 16 | 318058174, 17 | 468889844, 18 | 283034151, 19 | 293101857, 20 | 679084675, 21 | 218553052, 22 | 203186876, 23 | 360871416, 24 | 570673276, 25 | 862491437, 26 | 293114244, 27 | 297082563, 28 | 752573035, 29 | 206582661, 30 | 865335013, 31 | 696719165, 32 | 523820306, 33 | 283030833, 34 | 158328277, 35 | 607253439, 36 | 975241618, 37 | 794536233, 38 | 594808597, 39 | 591206513, 40 | 692024587, 41 | 301522681, 42 | 173266238, 43 | 541099855, 44 | 544155573, 45 | 278507621, 46 | 423152201, 47 | 530585715, 48 | 253540500, 49 | 282080994, 50 | 788604915, 51 | 361805480, 52 | 880543122, 53 | 297112260, 54 | 894361729, 55 | 974546183, 56 | 976916868, 57 | 742909989, 58 | 222289417, 59 | 681078312, 60 | 241515088, 61 | 311522444, 62 | 932846428, 63 | 741848923, 64 | 801055013, 65 | 730231483, 66 | 182924943, 67 | 428357078, 68 | 896991927, 69 | 682653438, 70 | 978929376, 71 | 922212269, 72 | 908372708, 73 | 493141904, 74 | 926986842, 75 | 954945418, 76 | 347953929, 77 | 690838889, 78 | 710907151, 79 | 563779544, 80 | 649489404, 81 | 551765049, 82 | 755823578, 83 | 403803235, 84 | 130651117, 85 | 985964767, 86 | 896341761, 87 | 322083917, 88 | 721147741, 89 | 644539794, 90 | 855205023, 91 | 669575245, 92 | 622728345, 93 | 369692819, 94 | 236822552, 95 | 535281861, 96 | 187246105, 97 | 238840786, 98 | 628098133, 99 | 126752913, 100 | 281330223, 101 | 410322847 102 | ] 103 | -------------------------------------------------------------------------------- /testdata/medium.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "person": { 4 | "id": "d50887ca-a6ce-4e59-b89f-14f0b5d03b03", 5 | "name": { 6 | "fullName": "Leonid Bugaev", 7 | "givenName": "Leonid", 8 | "familyName": "Bugaev" 9 | }, 10 | "email": "leonsbox@gmail.com", 11 | "gender": "male", 12 | "location": "Saint Petersburg, Saint Petersburg, RU", 13 | "geo": { 14 | "city": "Saint Petersburg", 15 | "state": "Saint Petersburg", 16 | "country": "Russia", 17 | "lat": 59.9342802, 18 | "lng": 30.3350986 19 | }, 20 | "bio": "Senior engineer at Granify.com", 21 | "site": "http://flickfaver.com", 22 | "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/d50887ca-a6ce-4e59-b89f-14f0b5d03b03", 23 | "employment": { 24 | "name": "www.latera.ru", 25 | "title": "Software Engineer", 26 | "domain": "gmail.com" 27 | }, 28 | "facebook": { 29 | "handle": "leonid.bugaev" 30 | }, 31 | "github": { 32 | "handle": "buger", 33 | "id": 14009, 34 | "avatar": "https://avatars.githubusercontent.com/u/14009?v=3", 35 | "company": "Granify", 36 | "blog": "http://leonsbox.com", 37 | "followers": 95, 38 | "following": 10 39 | }, 40 | "twitter": { 41 | "handle": "flickfaver", 42 | "id": 77004410, 43 | "bio": null, 44 | "followers": 2, 45 | "following": 1, 46 | "statuses": 5, 47 | "favorites": 0, 48 | "location": "", 49 | "site": "http://flickfaver.com", 50 | "avatar": null 51 | }, 52 | "linkedin": { 53 | "handle": "in/leonidbugaev" 54 | }, 55 | "googleplus": { 56 | "handle": null 57 | }, 58 | "angellist": { 59 | "handle": "leonid-bugaev", 60 | "id": 61541, 61 | "bio": "Senior engineer at Granify.com", 62 | "blog": "http://buger.github.com", 63 | "site": "http://buger.github.com", 64 | "followers": 41, 65 | "avatar": "https://d1qb2nb5cznatu.cloudfront.net/users/61541-medium_jpg?1405474390" 66 | }, 67 | "klout": { 68 | "handle": null, 69 | "score": null 70 | }, 71 | "foursquare": { 72 | "handle": null 73 | }, 74 | "aboutme": { 75 | "handle": "leonid.bugaev", 76 | "bio": null, 77 | "avatar": null 78 | }, 79 | "gravatar": { 80 | "handle": "buger", 81 | "urls": [ 82 | ], 83 | "avatar": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510", 84 | "avatars": [ 85 | { 86 | "url": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510", 87 | "type": "thumbnail" 88 | } 89 | ] 90 | }, 91 | "fuzzy": false 92 | }, 93 | "company": "hello" 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /testdata/nulls.json: -------------------------------------------------------------------------------- 1 | [ 2 | null, 3 | null, 4 | null, 5 | null, 6 | null, 7 | null, 8 | null, 9 | null, 10 | null, 11 | null, 12 | null, 13 | null, 14 | null, 15 | null, 16 | null, 17 | null, 18 | null, 19 | null, 20 | null, 21 | null, 22 | null, 23 | null, 24 | null, 25 | null, 26 | null, 27 | null, 28 | null, 29 | null, 30 | null, 31 | null, 32 | null, 33 | null, 34 | null, 35 | null, 36 | null, 37 | null, 38 | null, 39 | null, 40 | null, 41 | null, 42 | null, 43 | null, 44 | null, 45 | null, 46 | null, 47 | null, 48 | null, 49 | null, 50 | null, 51 | null, 52 | null, 53 | null, 54 | null, 55 | null, 56 | null, 57 | null, 58 | null, 59 | null, 60 | null, 61 | null, 62 | null, 63 | null, 64 | null, 65 | null, 66 | null, 67 | null, 68 | null, 69 | null, 70 | null, 71 | null, 72 | null, 73 | null, 74 | null, 75 | null, 76 | null, 77 | null, 78 | null, 79 | null, 80 | null, 81 | null, 82 | null, 83 | null, 84 | null, 85 | null, 86 | null, 87 | null, 88 | null, 89 | null, 90 | null, 91 | null, 92 | null, 93 | null, 94 | null, 95 | null, 96 | null, 97 | null, 98 | null, 99 | null, 100 | null, 101 | null 102 | ] 103 | 104 | -------------------------------------------------------------------------------- /testdata/otel_ex_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Timestamp": "1586960586000000000", 3 | "Attributes": { 4 | "http.status_code": 500, 5 | "http.url": "http://example.com", 6 | "my.custom.application.tag": "hello" 7 | }, 8 | "Resource": { 9 | "service.name": "donut_shop", 10 | "service.version": "2.0.0", 11 | "k8s.pod.uid": "1138528c-c36e-11e9-a1a7-42010a800198" 12 | }, 13 | "TraceId": "13e2a0921288b3ff80df0a0482d4fc46", 14 | "SpanId": "43222c2d51a7abe3", 15 | "SeverityText": "INFO", 16 | "SeverityNumber": 9, 17 | "Body": "20200415T072306-0700 INFO I like donuts" 18 | } 19 | -------------------------------------------------------------------------------- /testdata/slow_floats.json: -------------------------------------------------------------------------------- 1 | [ 2 | 0.6046602879796196, 3 | 0.9405090880450124, 4 | 0.6645600532184904, 5 | 0.4377141871869802, 6 | 0.4246374970712657, 7 | 0.6868230728671094, 8 | 0.06563701921747622, 9 | 0.15651925473279124, 10 | 0.09696951891448456, 11 | 0.30091186058528707, 12 | 0.5152126285020654, 13 | 0.8136399609900968, 14 | 0.21426387258237492, 15 | 0.380657189299686, 16 | 0.31805817433032985, 17 | 0.4688898449024232, 18 | 0.28303415118044517, 19 | 0.29310185733681576, 20 | 0.6790846759202163, 21 | 0.21855305259276428, 22 | 0.20318687664732285, 23 | 0.360871416856906, 24 | 0.5706732760710226, 25 | 0.8624914374478864, 26 | 0.29311424455385804, 27 | 0.29708256355629153, 28 | 0.7525730355516119, 29 | 0.2065826619136986, 30 | 0.865335013001561, 31 | 0.6967191657466347, 32 | 0.5238203060500009, 33 | 0.028303083325889995, 34 | 0.15832827774512764, 35 | 0.6072534395455154, 36 | 0.9752416188605784, 37 | 0.07945362337387198, 38 | 0.5948085976830626, 39 | 0.05912065131387529, 40 | 0.692024587353112, 41 | 0.30152268100656, 42 | 0.17326623818270528, 43 | 0.5410998550087353, 44 | 0.544155573000885, 45 | 0.27850762181610883, 46 | 0.4231522015718281, 47 | 0.5305857153507052, 48 | 0.2535405005150605, 49 | 0.28208099496492467, 50 | 0.7886049150193449, 51 | 0.3618054804803169, 52 | 0.8805431227416171, 53 | 0.2971122606397708, 54 | 0.8943617293304537, 55 | 0.09745461839911657, 56 | 0.9769168685862624, 57 | 0.07429099894984302, 58 | 0.22228941700678773, 59 | 0.6810783123925709, 60 | 0.24151508854715265, 61 | 0.31152244431052484, 62 | 0.932846428518434, 63 | 0.741848959991823, 64 | 0.8010550426526613, 65 | 0.7302314772948083, 66 | 0.18292491645390843, 67 | 0.4283570818068078, 68 | 0.8969919575618727, 69 | 0.6826534880132438, 70 | 0.9789293555766876, 71 | 0.9222122589217269, 72 | 0.09083727535388708, 73 | 0.4931419977048804, 74 | 0.9269868035744142, 75 | 0.9549454404167818, 76 | 0.3479539636282229, 77 | 0.6908388315056789, 78 | 0.7109071952999951, 79 | 0.5637795958152644, 80 | 0.6494894605929404, 81 | 0.5517650490127749, 82 | 0.7558235074915978, 83 | 0.40380328579570035, 84 | 0.13065111702897217, 85 | 0.9859647293402467, 86 | 0.8963417453962161, 87 | 0.3220839705208817, 88 | 0.7211477651926741, 89 | 0.6445397825093294, 90 | 0.08552050754191123, 91 | 0.6695752976997745, 92 | 0.6227283173637045, 93 | 0.3696928436398219, 94 | 0.2368225468054852, 95 | 0.5352818906344061, 96 | 0.18724610140105305, 97 | 0.2388407028053186, 98 | 0.6280981712183633, 99 | 0.1267529293726013, 100 | 0.28133029380535923, 101 | 0.41032284435628247 102 | ] 103 | -------------------------------------------------------------------------------- /testdata/small.json: -------------------------------------------------------------------------------- 1 | { 2 | "st": 1, 3 | "sid": 486, 4 | "tt": "active", 5 | "gr": 0, 6 | "uuid": "de305d54-75b4-431b-adb2-eb6b9e546014", 7 | "ip": "127.0.0.1", 8 | "ua": "user_agent", 9 | "tz": -6, 10 | "v": 1 11 | } 12 | -------------------------------------------------------------------------------- /testdata/test_parsing/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nicolas Seriot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_double_huge_neg_exp.json: -------------------------------------------------------------------------------- 1 | [123.456e-789] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_huge_exp.json: -------------------------------------------------------------------------------- 1 | [0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_neg_int_huge_exp.json: -------------------------------------------------------------------------------- 1 | [-1e+9999] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_pos_double_huge_exp.json: -------------------------------------------------------------------------------- 1 | [1.5e+9999] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_real_neg_overflow.json: -------------------------------------------------------------------------------- 1 | [-123123e100000] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_real_pos_overflow.json: -------------------------------------------------------------------------------- 1 | [123123e100000] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_real_underflow.json: -------------------------------------------------------------------------------- 1 | [123e-10000000] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_too_big_neg_int.json: -------------------------------------------------------------------------------- 1 | [-123123123123123123123123123123] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_too_big_pos_int.json: -------------------------------------------------------------------------------- 1 | [100000000000000000000] -------------------------------------------------------------------------------- /testdata/test_parsing/i_number_very_big_negative_int.json: -------------------------------------------------------------------------------- 1 | [-237462374673276894279832749832423479823246327846] -------------------------------------------------------------------------------- /testdata/test_parsing/i_object_key_lone_2nd_surrogate.json: -------------------------------------------------------------------------------- 1 | {"\uDFAA":0} -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_1st_surrogate_but_2nd_missing.json: -------------------------------------------------------------------------------- 1 | ["\uDADA"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json: -------------------------------------------------------------------------------- 1 | ["\uD888\u1234"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_UTF-16LE_with_BOM.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_UTF-16LE_with_BOM.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_UTF-8_invalid_sequence.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_UTF-8_invalid_sequence.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_UTF8_surrogate_U+D800.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_UTF8_surrogate_U+D800.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json: -------------------------------------------------------------------------------- 1 | ["\uD800\n"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_incomplete_surrogate_pair.json: -------------------------------------------------------------------------------- 1 | ["\uDd1ea"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_incomplete_surrogates_escape_valid.json: -------------------------------------------------------------------------------- 1 | ["\uD800\uD800\n"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_invalid_lonely_surrogate.json: -------------------------------------------------------------------------------- 1 | ["\ud800"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_invalid_surrogate.json: -------------------------------------------------------------------------------- 1 | ["\ud800abc"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_invalid_utf-8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_invalid_utf-8.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_inverted_surrogates_U+1D11E.json: -------------------------------------------------------------------------------- 1 | ["\uDd1e\uD834"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_iso_latin_1.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_iso_latin_1.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_lone_second_surrogate.json: -------------------------------------------------------------------------------- 1 | ["\uDFAA"] -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_lone_utf8_continuation_byte.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_lone_utf8_continuation_byte.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_not_in_unicode_range.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_not_in_unicode_range.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_overlong_sequence_2_bytes.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_overlong_sequence_2_bytes.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_overlong_sequence_6_bytes.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_overlong_sequence_6_bytes.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_overlong_sequence_6_bytes_null.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_overlong_sequence_6_bytes_null.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_truncated-utf-8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_truncated-utf-8.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_utf16BE_no_BOM.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_utf16BE_no_BOM.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_string_utf16LE_no_BOM.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/i_string_utf16LE_no_BOM.json -------------------------------------------------------------------------------- /testdata/test_parsing/i_structure_500_nested_arrays.json: -------------------------------------------------------------------------------- 1 | [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] -------------------------------------------------------------------------------- /testdata/test_parsing/i_structure_UTF-8_BOM_empty_object.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_1_true_without_comma.json: -------------------------------------------------------------------------------- 1 | [1 true] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_a_invalid_utf8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_array_a_invalid_utf8.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_colon_instead_of_comma.json: -------------------------------------------------------------------------------- 1 | ["": 1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_comma_after_close.json: -------------------------------------------------------------------------------- 1 | [""], -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_comma_and_number.json: -------------------------------------------------------------------------------- 1 | [,1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_double_comma.json: -------------------------------------------------------------------------------- 1 | [1,,2] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_double_extra_comma.json: -------------------------------------------------------------------------------- 1 | ["x",,] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_extra_close.json: -------------------------------------------------------------------------------- 1 | ["x"]] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_extra_comma.json: -------------------------------------------------------------------------------- 1 | ["",] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_incomplete.json: -------------------------------------------------------------------------------- 1 | ["x" -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_incomplete_invalid_value.json: -------------------------------------------------------------------------------- 1 | [x -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_inner_array_no_comma.json: -------------------------------------------------------------------------------- 1 | [3[4]] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_invalid_utf8.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_array_invalid_utf8.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_items_separated_by_semicolon.json: -------------------------------------------------------------------------------- 1 | [1:2] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_just_comma.json: -------------------------------------------------------------------------------- 1 | [,] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_just_minus.json: -------------------------------------------------------------------------------- 1 | [-] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_missing_value.json: -------------------------------------------------------------------------------- 1 | [ , ""] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_newlines_unclosed.json: -------------------------------------------------------------------------------- 1 | ["a", 2 | 4 3 | ,1, -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_number_and_comma.json: -------------------------------------------------------------------------------- 1 | [1,] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_number_and_several_commas.json: -------------------------------------------------------------------------------- 1 | [1,,] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_spaces_vertical_tab_formfeed.json: -------------------------------------------------------------------------------- 1 | [" a"\f] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_star_inside.json: -------------------------------------------------------------------------------- 1 | [*] -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_unclosed.json: -------------------------------------------------------------------------------- 1 | ["" -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_unclosed_trailing_comma.json: -------------------------------------------------------------------------------- 1 | [1, -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_unclosed_with_new_lines.json: -------------------------------------------------------------------------------- 1 | [1, 2 | 1 3 | ,1 -------------------------------------------------------------------------------- /testdata/test_parsing/n_array_unclosed_with_object_inside.json: -------------------------------------------------------------------------------- 1 | [{} -------------------------------------------------------------------------------- /testdata/test_parsing/n_incomplete_false.json: -------------------------------------------------------------------------------- 1 | [fals] -------------------------------------------------------------------------------- /testdata/test_parsing/n_incomplete_null.json: -------------------------------------------------------------------------------- 1 | [nul] -------------------------------------------------------------------------------- /testdata/test_parsing/n_incomplete_true.json: -------------------------------------------------------------------------------- 1 | [tru] -------------------------------------------------------------------------------- /testdata/test_parsing/n_multidigit_number_then_00.json: -------------------------------------------------------------------------------- 1 | 123 -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_++.json: -------------------------------------------------------------------------------- 1 | [++1234] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_+1.json: -------------------------------------------------------------------------------- 1 | [+1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_+Inf.json: -------------------------------------------------------------------------------- 1 | [+Inf] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_-01.json: -------------------------------------------------------------------------------- 1 | [-01] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_-1.0..json: -------------------------------------------------------------------------------- 1 | [-1.0.] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_-2..json: -------------------------------------------------------------------------------- 1 | [-2.] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_-NaN.json: -------------------------------------------------------------------------------- 1 | [-NaN] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_.-1.json: -------------------------------------------------------------------------------- 1 | [.-1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_.2e-3.json: -------------------------------------------------------------------------------- 1 | [.2e-3] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0.1.2.json: -------------------------------------------------------------------------------- 1 | [0.1.2] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0.3e+.json: -------------------------------------------------------------------------------- 1 | [0.3e+] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0.3e.json: -------------------------------------------------------------------------------- 1 | [0.3e] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0.e1.json: -------------------------------------------------------------------------------- 1 | [0.e1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0_capital_E+.json: -------------------------------------------------------------------------------- 1 | [0E+] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0_capital_E.json: -------------------------------------------------------------------------------- 1 | [0E] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0e+.json: -------------------------------------------------------------------------------- 1 | [0e+] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_0e.json: -------------------------------------------------------------------------------- 1 | [0e] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_1.0e+.json: -------------------------------------------------------------------------------- 1 | [1.0e+] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_1.0e-.json: -------------------------------------------------------------------------------- 1 | [1.0e-] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_1.0e.json: -------------------------------------------------------------------------------- 1 | [1.0e] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_1_000.json: -------------------------------------------------------------------------------- 1 | [1 000.0] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_1eE2.json: -------------------------------------------------------------------------------- 1 | [1eE2] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_2.e+3.json: -------------------------------------------------------------------------------- 1 | [2.e+3] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_2.e-3.json: -------------------------------------------------------------------------------- 1 | [2.e-3] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_2.e3.json: -------------------------------------------------------------------------------- 1 | [2.e3] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_9.e+.json: -------------------------------------------------------------------------------- 1 | [9.e+] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_Inf.json: -------------------------------------------------------------------------------- 1 | [Inf] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_NaN.json: -------------------------------------------------------------------------------- 1 | [NaN] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_U+FF11_fullwidth_digit_one.json: -------------------------------------------------------------------------------- 1 | [1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_expression.json: -------------------------------------------------------------------------------- 1 | [1+2] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_hex_1_digit.json: -------------------------------------------------------------------------------- 1 | [0x1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_hex_2_digits.json: -------------------------------------------------------------------------------- 1 | [0x42] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_infinity.json: -------------------------------------------------------------------------------- 1 | [Infinity] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_invalid+-.json: -------------------------------------------------------------------------------- 1 | [0e+-1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_invalid-negative-real.json: -------------------------------------------------------------------------------- 1 | [-123.123foo] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_invalid-utf-8-in-bigger-int.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_number_invalid-utf-8-in-bigger-int.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_invalid-utf-8-in-exponent.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_number_invalid-utf-8-in-exponent.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_invalid-utf-8-in-int.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_number_invalid-utf-8-in-int.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_minus_infinity.json: -------------------------------------------------------------------------------- 1 | [-Infinity] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_minus_sign_with_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | [-foo] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_minus_space_1.json: -------------------------------------------------------------------------------- 1 | [- 1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_neg_int_starting_with_zero.json: -------------------------------------------------------------------------------- 1 | [-012] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_neg_real_without_int_part.json: -------------------------------------------------------------------------------- 1 | [-.123] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_neg_with_garbage_at_end.json: -------------------------------------------------------------------------------- 1 | [-1x] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_real_garbage_after_e.json: -------------------------------------------------------------------------------- 1 | [1ea] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_real_with_invalid_utf8_after_e.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_number_real_with_invalid_utf8_after_e.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_real_without_fractional_part.json: -------------------------------------------------------------------------------- 1 | [1.] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_starting_with_dot.json: -------------------------------------------------------------------------------- 1 | [.123] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_with_alpha.json: -------------------------------------------------------------------------------- 1 | [1.2a-3] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_with_alpha_char.json: -------------------------------------------------------------------------------- 1 | [1.8011670033376514H-308] -------------------------------------------------------------------------------- /testdata/test_parsing/n_number_with_leading_zero.json: -------------------------------------------------------------------------------- 1 | [012] -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_bad_value.json: -------------------------------------------------------------------------------- 1 | ["x", truth] -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_bracket_key.json: -------------------------------------------------------------------------------- 1 | {[: "x"} 2 | -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_comma_instead_of_colon.json: -------------------------------------------------------------------------------- 1 | {"x", null} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_double_colon.json: -------------------------------------------------------------------------------- 1 | {"x"::"b"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_emoji.json: -------------------------------------------------------------------------------- 1 | {🇨🇭} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_garbage_at_end.json: -------------------------------------------------------------------------------- 1 | {"a":"a" 123} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_key_with_single_quotes.json: -------------------------------------------------------------------------------- 1 | {key: 'value'} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_missing_colon.json: -------------------------------------------------------------------------------- 1 | {"a" b} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_missing_key.json: -------------------------------------------------------------------------------- 1 | {:"b"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_missing_semicolon.json: -------------------------------------------------------------------------------- 1 | {"a" "b"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_missing_value.json: -------------------------------------------------------------------------------- 1 | {"a": -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_no-colon.json: -------------------------------------------------------------------------------- 1 | {"a" -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_non_string_key.json: -------------------------------------------------------------------------------- 1 | {1:1} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_non_string_key_but_huge_number_instead.json: -------------------------------------------------------------------------------- 1 | {9999E9999:1} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_repeated_null_null.json: -------------------------------------------------------------------------------- 1 | {null:null,null:null} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_several_trailing_commas.json: -------------------------------------------------------------------------------- 1 | {"id":0,,,,,} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_single_quote.json: -------------------------------------------------------------------------------- 1 | {'a':0} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_trailing_comma.json: -------------------------------------------------------------------------------- 1 | {"id":0,} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_trailing_comment.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}/**/ -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_trailing_comment_open.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}/**// -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_trailing_comment_slash_open.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}// -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_trailing_comment_slash_open_incomplete.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}/ -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_two_commas_in_a_row.json: -------------------------------------------------------------------------------- 1 | {"a":"b",,"c":"d"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_unquoted_key.json: -------------------------------------------------------------------------------- 1 | {a: "b"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_unterminated-value.json: -------------------------------------------------------------------------------- 1 | {"a":"a -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_with_single_string.json: -------------------------------------------------------------------------------- 1 | { "foo" : "bar", "a" } -------------------------------------------------------------------------------- /testdata/test_parsing/n_object_with_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}# -------------------------------------------------------------------------------- /testdata/test_parsing/n_single_space.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_1_surrogate_then_escape.json: -------------------------------------------------------------------------------- 1 | ["\uD800\"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_1_surrogate_then_escape_u.json: -------------------------------------------------------------------------------- 1 | ["\uD800\u"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_1_surrogate_then_escape_u1.json: -------------------------------------------------------------------------------- 1 | ["\uD800\u1"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_1_surrogate_then_escape_u1x.json: -------------------------------------------------------------------------------- 1 | ["\uD800\u1x"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_accentuated_char_no_quotes.json: -------------------------------------------------------------------------------- 1 | [é] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_backslash_00.json: -------------------------------------------------------------------------------- 1 | ["\"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_escape_x.json: -------------------------------------------------------------------------------- 1 | ["\x00"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_escaped_backslash_bad.json: -------------------------------------------------------------------------------- 1 | ["\\\"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_escaped_ctrl_char_tab.json: -------------------------------------------------------------------------------- 1 | ["\ "] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_escaped_emoji.json: -------------------------------------------------------------------------------- 1 | ["\🌀"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_incomplete_escape.json: -------------------------------------------------------------------------------- 1 | ["\"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_incomplete_escaped_character.json: -------------------------------------------------------------------------------- 1 | ["\u00A"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_incomplete_surrogate.json: -------------------------------------------------------------------------------- 1 | ["\uD834\uDd"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_incomplete_surrogate_escape_invalid.json: -------------------------------------------------------------------------------- 1 | ["\uD800\uD800\x"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_invalid-utf-8-in-escape.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_string_invalid-utf-8-in-escape.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_invalid_backslash_esc.json: -------------------------------------------------------------------------------- 1 | ["\a"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_invalid_unicode_escape.json: -------------------------------------------------------------------------------- 1 | ["\uqqqq"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_invalid_utf8_after_escape.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_string_invalid_utf8_after_escape.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_leading_uescaped_thinspace.json: -------------------------------------------------------------------------------- 1 | [\u0020"asd"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_no_quotes_with_bad_escape.json: -------------------------------------------------------------------------------- 1 | [\n] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_single_doublequote.json: -------------------------------------------------------------------------------- 1 | " -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_single_quote.json: -------------------------------------------------------------------------------- 1 | ['single quote'] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_single_string_no_double_quotes.json: -------------------------------------------------------------------------------- 1 | abc -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_start_escape_unclosed.json: -------------------------------------------------------------------------------- 1 | ["\ -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_unescaped_ctrl_char.json: -------------------------------------------------------------------------------- 1 | ["aa"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_unescaped_newline.json: -------------------------------------------------------------------------------- 1 | ["new 2 | line"] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_unescaped_tab.json: -------------------------------------------------------------------------------- 1 | [" "] -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_unicode_CapitalU.json: -------------------------------------------------------------------------------- 1 | "\UA66D" -------------------------------------------------------------------------------- /testdata/test_parsing/n_string_with_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | ""x -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_U+2060_word_joined.json: -------------------------------------------------------------------------------- 1 | [⁠] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_UTF8_BOM_no_data.json: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_angle_bracket_..json: -------------------------------------------------------------------------------- 1 | <.> -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_angle_bracket_null.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_array_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | [1]x -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_array_with_extra_array_close.json: -------------------------------------------------------------------------------- 1 | [1]] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_array_with_unclosed_string.json: -------------------------------------------------------------------------------- 1 | ["asd] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_ascii-unicode-identifier.json: -------------------------------------------------------------------------------- 1 | aå -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_capitalized_True.json: -------------------------------------------------------------------------------- 1 | [True] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_close_unopened_array.json: -------------------------------------------------------------------------------- 1 | 1] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_comma_instead_of_closing_brace.json: -------------------------------------------------------------------------------- 1 | {"x": true, -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_double_array.json: -------------------------------------------------------------------------------- 1 | [][] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_end_array.json: -------------------------------------------------------------------------------- 1 | ] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_incomplete_UTF8_BOM.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_structure_incomplete_UTF8_BOM.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_lone-invalid-utf-8.json: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_lone-open-bracket.json: -------------------------------------------------------------------------------- 1 | [ -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_no_data.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-faster/jx/372d595b2a1eaf660e3e5ebae7952b45350fcbc3/testdata/test_parsing/n_structure_no_data.json -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_null-byte-outside-string.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_number_with_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | 2@ -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_object_followed_by_closing_object.json: -------------------------------------------------------------------------------- 1 | {}} -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_object_unclosed_no_value.json: -------------------------------------------------------------------------------- 1 | {"": -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_object_with_comment.json: -------------------------------------------------------------------------------- 1 | {"a":/*comment*/"b"} -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_object_with_trailing_garbage.json: -------------------------------------------------------------------------------- 1 | {"a": true} "x" -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_array_apostrophe.json: -------------------------------------------------------------------------------- 1 | [' -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_array_comma.json: -------------------------------------------------------------------------------- 1 | [, -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_array_open_object.json: -------------------------------------------------------------------------------- 1 | [{ -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_array_open_string.json: -------------------------------------------------------------------------------- 1 | ["a -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_array_string.json: -------------------------------------------------------------------------------- 1 | ["a" -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object.json: -------------------------------------------------------------------------------- 1 | { -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object_close_array.json: -------------------------------------------------------------------------------- 1 | {] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object_comma.json: -------------------------------------------------------------------------------- 1 | {, -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object_open_array.json: -------------------------------------------------------------------------------- 1 | {[ -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object_open_string.json: -------------------------------------------------------------------------------- 1 | {"a -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_object_string_with_apostrophes.json: -------------------------------------------------------------------------------- 1 | {'a' -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_open_open.json: -------------------------------------------------------------------------------- 1 | ["\{["\{["\{["\{ -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_single_eacute.json: -------------------------------------------------------------------------------- 1 | � -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_single_star.json: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_trailing_#.json: -------------------------------------------------------------------------------- 1 | {"a":"b"}#{} -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_uescaped_LF_before_string.json: -------------------------------------------------------------------------------- 1 | [\u000A""] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unclosed_array.json: -------------------------------------------------------------------------------- 1 | [1 -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unclosed_array_partial_null.json: -------------------------------------------------------------------------------- 1 | [ false, nul -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unclosed_array_unfinished_false.json: -------------------------------------------------------------------------------- 1 | [ true, fals -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unclosed_array_unfinished_true.json: -------------------------------------------------------------------------------- 1 | [ false, tru -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unclosed_object.json: -------------------------------------------------------------------------------- 1 | {"asd":"asd" -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_unicode-identifier.json: -------------------------------------------------------------------------------- 1 | å -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_whitespace_U+2060_word_joiner.json: -------------------------------------------------------------------------------- 1 | [⁠] -------------------------------------------------------------------------------- /testdata/test_parsing/n_structure_whitespace_formfeed.json: -------------------------------------------------------------------------------- 1 | [ ] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_arraysWithSpaces.json: -------------------------------------------------------------------------------- 1 | [[] ] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_empty-string.json: -------------------------------------------------------------------------------- 1 | [""] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_empty.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_ending_with_newline.json: -------------------------------------------------------------------------------- 1 | ["a"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_false.json: -------------------------------------------------------------------------------- 1 | [false] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_heterogeneous.json: -------------------------------------------------------------------------------- 1 | [null, 1, "1", {}] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_null.json: -------------------------------------------------------------------------------- 1 | [null] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_with_1_and_newline.json: -------------------------------------------------------------------------------- 1 | [1 2 | ] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_with_leading_space.json: -------------------------------------------------------------------------------- 1 | [1] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_with_several_null.json: -------------------------------------------------------------------------------- 1 | [1,null,null,null,2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_array_with_trailing_space.json: -------------------------------------------------------------------------------- 1 | [2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number.json: -------------------------------------------------------------------------------- 1 | [123e65] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_0e+1.json: -------------------------------------------------------------------------------- 1 | [0e+1] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_0e1.json: -------------------------------------------------------------------------------- 1 | [0e1] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_after_space.json: -------------------------------------------------------------------------------- 1 | [ 4] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_double_close_to_zero.json: -------------------------------------------------------------------------------- 1 | [-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] 2 | -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_int_with_exp.json: -------------------------------------------------------------------------------- 1 | [20e1] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_minus_zero.json: -------------------------------------------------------------------------------- 1 | [-0] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_negative_int.json: -------------------------------------------------------------------------------- 1 | [-123] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_negative_one.json: -------------------------------------------------------------------------------- 1 | [-1] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_negative_zero.json: -------------------------------------------------------------------------------- 1 | [-0] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_capital_e.json: -------------------------------------------------------------------------------- 1 | [1E22] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_capital_e_neg_exp.json: -------------------------------------------------------------------------------- 1 | [1E-2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_capital_e_pos_exp.json: -------------------------------------------------------------------------------- 1 | [1E+2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_exponent.json: -------------------------------------------------------------------------------- 1 | [123e45] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_fraction_exponent.json: -------------------------------------------------------------------------------- 1 | [123.456e78] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_neg_exp.json: -------------------------------------------------------------------------------- 1 | [1e-2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_real_pos_exponent.json: -------------------------------------------------------------------------------- 1 | [1e+2] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_simple_int.json: -------------------------------------------------------------------------------- 1 | [123] -------------------------------------------------------------------------------- /testdata/test_parsing/y_number_simple_real.json: -------------------------------------------------------------------------------- 1 | [123.456789] -------------------------------------------------------------------------------- /testdata/test_parsing/y_object.json: -------------------------------------------------------------------------------- 1 | {"asd":"sdf", "dfg":"fgh"} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_basic.json: -------------------------------------------------------------------------------- 1 | {"asd":"sdf"} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_duplicated_key.json: -------------------------------------------------------------------------------- 1 | {"a":"b","a":"c"} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_duplicated_key_and_value.json: -------------------------------------------------------------------------------- 1 | {"a":"b","a":"b"} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_empty.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_empty_key.json: -------------------------------------------------------------------------------- 1 | {"":0} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_escaped_null_in_key.json: -------------------------------------------------------------------------------- 1 | {"foo\u0000bar": 42} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_extreme_numbers.json: -------------------------------------------------------------------------------- 1 | { "min": -1.0e+28, "max": 1.0e+28 } -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_long_strings.json: -------------------------------------------------------------------------------- 1 | {"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_simple.json: -------------------------------------------------------------------------------- 1 | {"a":[]} -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_string_unicode.json: -------------------------------------------------------------------------------- 1 | {"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } -------------------------------------------------------------------------------- /testdata/test_parsing/y_object_with_newlines.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "b" 3 | } -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json: -------------------------------------------------------------------------------- 1 | ["\u0060\u012a\u12AB"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_accepted_surrogate_pair.json: -------------------------------------------------------------------------------- 1 | ["\uD801\udc37"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_accepted_surrogate_pairs.json: -------------------------------------------------------------------------------- 1 | ["\ud83d\ude39\ud83d\udc8d"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_allowed_escapes.json: -------------------------------------------------------------------------------- 1 | ["\"\\\/\b\f\n\r\t"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_backslash_and_u_escaped_zero.json: -------------------------------------------------------------------------------- 1 | ["\\u0000"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_backslash_doublequotes.json: -------------------------------------------------------------------------------- 1 | ["\""] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_comments.json: -------------------------------------------------------------------------------- 1 | ["a/*b*/c/*d//e"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_double_escape_a.json: -------------------------------------------------------------------------------- 1 | ["\\a"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_double_escape_n.json: -------------------------------------------------------------------------------- 1 | ["\\n"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_escaped_control_character.json: -------------------------------------------------------------------------------- 1 | ["\u0012"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_escaped_noncharacter.json: -------------------------------------------------------------------------------- 1 | ["\uFFFF"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_in_array.json: -------------------------------------------------------------------------------- 1 | ["asd"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_in_array_with_leading_space.json: -------------------------------------------------------------------------------- 1 | [ "asd"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_last_surrogates_1_and_2.json: -------------------------------------------------------------------------------- 1 | ["\uDBFF\uDFFF"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_nbsp_uescaped.json: -------------------------------------------------------------------------------- 1 | ["new\u00A0line"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json: -------------------------------------------------------------------------------- 1 | ["􏿿"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json: -------------------------------------------------------------------------------- 1 | ["￿"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_null_escape.json: -------------------------------------------------------------------------------- 1 | ["\u0000"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_one-byte-utf-8.json: -------------------------------------------------------------------------------- 1 | ["\u002c"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_pi.json: -------------------------------------------------------------------------------- 1 | ["π"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json: -------------------------------------------------------------------------------- 1 | ["𛿿"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_simple_ascii.json: -------------------------------------------------------------------------------- 1 | ["asd "] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_space.json: -------------------------------------------------------------------------------- 1 | " " -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json: -------------------------------------------------------------------------------- 1 | ["\uD834\uDd1e"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_three-byte-utf-8.json: -------------------------------------------------------------------------------- 1 | ["\u0821"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_two-byte-utf-8.json: -------------------------------------------------------------------------------- 1 | ["\u0123"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_u+2028_line_sep.json: -------------------------------------------------------------------------------- 1 | ["
"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_u+2029_par_sep.json: -------------------------------------------------------------------------------- 1 | ["
"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_uEscape.json: -------------------------------------------------------------------------------- 1 | ["\u0061\u30af\u30EA\u30b9"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_uescaped_newline.json: -------------------------------------------------------------------------------- 1 | ["new\u000Aline"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unescaped_char_delete.json: -------------------------------------------------------------------------------- 1 | [""] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode.json: -------------------------------------------------------------------------------- 1 | ["\uA66D"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicodeEscapedBackslash.json: -------------------------------------------------------------------------------- 1 | ["\u005C"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_2.json: -------------------------------------------------------------------------------- 1 | ["⍂㈴⍂"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+10FFFE_nonchar.json: -------------------------------------------------------------------------------- 1 | ["\uDBFF\uDFFE"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+1FFFE_nonchar.json: -------------------------------------------------------------------------------- 1 | ["\uD83F\uDFFE"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json: -------------------------------------------------------------------------------- 1 | ["\u200B"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+2064_invisible_plus.json: -------------------------------------------------------------------------------- 1 | ["\u2064"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+FDD0_nonchar.json: -------------------------------------------------------------------------------- 1 | ["\uFDD0"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_U+FFFE_nonchar.json: -------------------------------------------------------------------------------- 1 | ["\uFFFE"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_unicode_escaped_double_quote.json: -------------------------------------------------------------------------------- 1 | ["\u0022"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_utf8.json: -------------------------------------------------------------------------------- 1 | ["€𝄞"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_string_with_del_character.json: -------------------------------------------------------------------------------- 1 | ["aa"] -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_false.json: -------------------------------------------------------------------------------- 1 | false -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_int.json: -------------------------------------------------------------------------------- 1 | 42 -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_negative_real.json: -------------------------------------------------------------------------------- 1 | -0.1 -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_null.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_string.json: -------------------------------------------------------------------------------- 1 | "asd" -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_lonely_true.json: -------------------------------------------------------------------------------- 1 | true -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_string_empty.json: -------------------------------------------------------------------------------- 1 | "" -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_trailing_newline.json: -------------------------------------------------------------------------------- 1 | ["a"] 2 | -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_true_in_array.json: -------------------------------------------------------------------------------- 1 | [true] -------------------------------------------------------------------------------- /testdata/test_parsing/y_structure_whitespace_array.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /testdata/tiny.json: -------------------------------------------------------------------------------- 1 | {"x":0} 2 | -------------------------------------------------------------------------------- /tools/mkint/encode.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{- /*gotype: github.com/go-faster/jx/tools/mkint.Config*/ -}} 3 | // Code generated by mkint, DO NOT EDIT. 4 | 5 | package {{ $.PackageName }} 6 | 7 | var digits []uint32 8 | 9 | func init() { 10 | digits = make([]uint32, 1000) 11 | for i := uint32(0); i < 1000; i++ { 12 | digits[i] = (((i / 100) + '0') << 16) + ((((i / 10) % 10) + '0') << 8) + i%10 + '0' 13 | if i < 10 { 14 | digits[i] += 2 << 24 15 | } else if i < 100 { 16 | digits[i] += 1 << 24 17 | } 18 | } 19 | } 20 | 21 | func writeFirstBuf(w *Writer, v uint32) bool { 22 | r := make([]byte, 0, 4) 23 | start := v >> 24 24 | if start == 0 { 25 | r = append(r, byte(v>>16), byte(v>>8)) 26 | } else if start == 1 { 27 | r = append(r, byte(v>>8)) 28 | } 29 | r = append(r, byte(v)) 30 | return writeStreamBytes(w, r...) 31 | } 32 | 33 | func writeBuf(w *Writer, v uint32) bool { 34 | return writeStreamBytes(w, byte(v>>16), byte(v>>8), byte(v)) 35 | } 36 | 37 | {{ range $typ := $.Types }} 38 | {{ template "encode_uint" $typ }} 39 | {{ template "encode_int" $typ }} 40 | {{- end }} 41 | 42 | {{ end }} 43 | 44 | {{ define "encode_uint" }} 45 | {{- /*gotype: github.com/go-faster/jx/tools/mkint.IntType */ -}} 46 | // U{{ title $.Name }} encodes u{{ $.Name }}. 47 | func (w *Writer) U{{ title $.Name }}(v u{{ $.Name }}) (fail bool) { 48 | q0 := v 49 | {{- range $i, $_ := times $.EncoderIterations }} 50 | // Iteration {{ $i }}. 51 | {{- if not (eq $i 0) }} 52 | r{{$i}} := q{{add $i -1}} - q{{$i}}*1000 53 | {{- end }} 54 | 55 | {{- if eq $i (sub $.EncoderIterations 1) }} 56 | fail = fail || writeFirstBuf(w, digits[q{{$i}}]) 57 | {{- range $r, $_ := times $i }} 58 | fail = fail || writeBuf(w, digits[r{{sub $i $r}}]) 59 | {{- end }} 60 | {{- else }} 61 | q{{add $i 1}} := q{{$i}} / 1000 62 | if q{{add $i 1}} == 0 { 63 | fail = fail || writeFirstBuf(w, digits[q{{$i}}]) 64 | {{- range $r, $_ := times $i }} 65 | fail = fail || writeBuf(w, digits[r{{sub $i $r}}]) 66 | {{- end }} 67 | return fail 68 | } 69 | {{- end }} 70 | {{- end }} 71 | return fail 72 | } 73 | 74 | // U{{ title $.Name }} encodes u{{ $.Name }}. 75 | func (e *Encoder) U{{ title $.Name }}(v u{{ $.Name }}) bool { 76 | return e.comma() || e.w.U{{ title $.Name }}(v) 77 | } 78 | {{ end }} 79 | 80 | {{ define "encode_int" }} 81 | {{- /*gotype: github.com/go-faster/jx/tools/mkint.IntType */ -}} 82 | // {{ title $.Name }} encodes {{ $.Name }}. 83 | func (w *Writer) {{ title $.Name }}(v {{ $.Name }}) (fail bool) { 84 | var val u{{ $.Name }} 85 | if v < 0 { 86 | val = u{{ $.Name }}(-v) 87 | fail = w.byte('-') 88 | } else { 89 | val = u{{ $.Name }}(v) 90 | } 91 | return fail || w.U{{ title $.Name }}(val) 92 | } 93 | 94 | // {{ title $.Name }} encodes {{ $.Name }}. 95 | func (e *Encoder) {{ title $.Name }}(v {{ $.Name }}) bool { 96 | return e.comma() || e.w.{{ title $.Name }}(v) 97 | } 98 | {{ end }} 99 | -------------------------------------------------------------------------------- /w.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // Writer writes json tokens to underlying buffer. 9 | // 10 | // Zero value is valid. 11 | type Writer struct { 12 | Buf []byte // underlying buffer 13 | stream *streamState 14 | } 15 | 16 | // Write implements io.Writer. 17 | func (w *Writer) Write(p []byte) (n int, err error) { 18 | if w.stream != nil { 19 | return 0, errStreaming 20 | } 21 | w.Buf = append(w.Buf, p...) 22 | return len(p), nil 23 | } 24 | 25 | // WriteTo implements io.WriterTo. 26 | func (w *Writer) WriteTo(t io.Writer) (n int64, err error) { 27 | if w.stream != nil { 28 | return 0, errStreaming 29 | } 30 | wrote, err := t.Write(w.Buf) 31 | return int64(wrote), err 32 | } 33 | 34 | // String returns string of underlying buffer. 35 | func (w Writer) String() string { 36 | w.stream.mustNotBeStreaming() 37 | return string(w.Buf) 38 | } 39 | 40 | // Reset resets underlying buffer. 41 | // 42 | // If w is in streaming mode, it is reset to non-streaming mode. 43 | func (w *Writer) Reset() { 44 | w.Buf = w.Buf[:0] 45 | w.stream = nil 46 | } 47 | 48 | // ResetWriter resets underlying buffer and sets output writer. 49 | func (w *Writer) ResetWriter(out io.Writer) { 50 | w.Buf = w.Buf[:0] 51 | if w.stream == nil { 52 | w.stream = newStreamState(out) 53 | } 54 | w.stream.Reset(out) 55 | } 56 | 57 | // Grow grows the underlying buffer. 58 | // 59 | // Calls (*bytes.Buffer).Grow(n int) on w.Buf. 60 | func (w *Writer) Grow(n int) { 61 | buf := bytes.NewBuffer(w.Buf) 62 | buf.Grow(n) 63 | w.Buf = buf.Bytes() 64 | } 65 | 66 | // byte writes a single byte. 67 | func (w *Writer) byte(c byte) (fail bool) { 68 | if w.stream == nil { 69 | w.Buf = append(w.Buf, c) 70 | return false 71 | } 72 | return writeStreamBytes(w, c) 73 | } 74 | 75 | func (w *Writer) twoBytes(c1, c2 byte) bool { 76 | if w.stream == nil { 77 | w.Buf = append(w.Buf, c1, c2) 78 | return false 79 | } 80 | return writeStreamBytes(w, c1, c2) 81 | } 82 | 83 | // RawStr writes string as raw json. 84 | func (w *Writer) RawStr(v string) bool { 85 | return w.rawStr(v) 86 | } 87 | 88 | func (w *Writer) rawStr(v string) bool { 89 | return writeStreamByteseq(w, v) 90 | } 91 | 92 | // Raw writes byte slice as raw json. 93 | func (w *Writer) Raw(b []byte) bool { 94 | return writeStreamByteseq(w, b) 95 | } 96 | 97 | // Null writes null. 98 | func (w *Writer) Null() bool { 99 | return writeStreamByteseq(w, "null") 100 | } 101 | 102 | // True writes true. 103 | func (w *Writer) True() bool { 104 | return writeStreamByteseq(w, "true") 105 | } 106 | 107 | // False writes false. 108 | func (w *Writer) False() bool { 109 | return writeStreamByteseq(w, "false") 110 | } 111 | 112 | // Bool encodes boolean. 113 | func (w *Writer) Bool(v bool) bool { 114 | if v { 115 | return w.True() 116 | } 117 | return w.False() 118 | } 119 | 120 | // ObjStart writes object start. 121 | func (w *Writer) ObjStart() bool { 122 | return w.byte('{') 123 | } 124 | 125 | // FieldStart encodes field name and writes colon. 126 | func (w *Writer) FieldStart(field string) bool { 127 | return w.Str(field) || 128 | w.byte(':') 129 | } 130 | 131 | // ObjEnd writes end of object token. 132 | func (w *Writer) ObjEnd() bool { 133 | return w.byte('}') 134 | } 135 | 136 | // ArrStart writes start of array. 137 | func (w *Writer) ArrStart() bool { 138 | return w.byte('[') 139 | } 140 | 141 | // ArrEnd writes end of array. 142 | func (w *Writer) ArrEnd() bool { 143 | return w.byte(']') 144 | } 145 | 146 | // Comma writes comma. 147 | func (w *Writer) Comma() bool { 148 | return w.byte(',') 149 | } 150 | -------------------------------------------------------------------------------- /w_b64.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | stdbase64 "encoding/base64" 5 | 6 | "github.com/segmentio/asm/base64" 7 | ) 8 | 9 | // Base64 encodes data as standard base64 encoded string. 10 | // 11 | // Same as encoding/json, base64.StdEncoding or RFC 4648. 12 | func (w *Writer) Base64(data []byte) bool { 13 | if data == nil { 14 | return w.Null() 15 | } 16 | 17 | if w.byte('"') { 18 | return true 19 | } 20 | 21 | encodedLen := base64.StdEncoding.EncodedLen(len(data)) 22 | switch { 23 | case w.stream == nil || len(w.Buf)+encodedLen <= cap(w.Buf): 24 | start := len(w.Buf) 25 | w.Buf = append(w.Buf, make([]byte, encodedLen)...) 26 | base64.StdEncoding.Encode(w.Buf[start:], data) 27 | default: 28 | s := w.stream 29 | 30 | var fail bool 31 | w.Buf, fail = s.flush(w.Buf) 32 | if fail { 33 | return true 34 | } 35 | e := stdbase64.NewEncoder(stdbase64.StdEncoding, s.writer) 36 | if _, err := e.Write(data); err != nil { 37 | s.setError(err) 38 | return true 39 | } 40 | if err := e.Close(); err != nil { 41 | s.setError(err) 42 | return true 43 | } 44 | } 45 | 46 | return w.byte('"') 47 | } 48 | -------------------------------------------------------------------------------- /w_b64_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/go-faster/errors" 13 | ) 14 | 15 | type limitWriter struct { 16 | w io.Writer 17 | n int64 18 | } 19 | 20 | func (t *limitWriter) Write(p []byte) (n int, err error) { 21 | if t.n-int64(len(p)) < 0 { 22 | return 0, errors.New("limit reached") 23 | } 24 | // real write 25 | n = len(p) 26 | if int64(n) > t.n { 27 | n = int(t.n) 28 | } 29 | n, err = t.w.Write(p[0:n]) 30 | t.n -= int64(n) 31 | if err == nil { 32 | n = len(p) 33 | } 34 | return 35 | } 36 | 37 | func TestWriter_Base64(t *testing.T) { 38 | const bufSize = minEncoderBufSize 39 | const fieldLength = bufSize - len(`{"":`) 40 | 41 | limits := []int64{ 42 | 31, // write '"' 43 | 32, // flush 44 | 33, // Write base64 45 | 73, // Write tail of base64 46 | } 47 | 48 | data := bytes.Repeat([]byte{0}, bufSize) 49 | for _, n := range limits { 50 | // Write '"' error. 51 | e := NewStreamingEncoder(&limitWriter{w: io.Discard, n: n}, bufSize) 52 | e.Obj(func(e *Encoder) { 53 | e.FieldStart(strings.Repeat("a", fieldLength)) 54 | e.Base64(data) 55 | }) 56 | require.Error(t, e.Close()) 57 | } 58 | } 59 | 60 | func BenchmarkWriter_Base64(b *testing.B) { 61 | for _, n := range []int{ 62 | 128, 63 | 256, 64 | 512, 65 | 1024, 66 | } { 67 | b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { 68 | var v []byte 69 | for i := 0; i < n; i++ { 70 | v = append(v, byte(i%256)) 71 | } 72 | 73 | b.ReportAllocs() 74 | b.SetBytes(int64(n)) 75 | var w Writer 76 | for i := 0; i < b.N; i++ { 77 | w.Base64(v) 78 | w.Reset() 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /w_float.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Float32 encodes float32. 4 | // 5 | // NB: Infinities and NaN are represented as null. 6 | func (w *Writer) Float32(v float32) bool { return w.Float(float64(v), 32) } 7 | 8 | // Float64 encodes float64. 9 | // 10 | // NB: Infinities and NaN are represented as null. 11 | func (w *Writer) Float64(v float64) bool { return w.Float(v, 64) } 12 | -------------------------------------------------------------------------------- /w_float_bits.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package jx 6 | 7 | import ( 8 | "math" 9 | "strconv" 10 | ) 11 | 12 | // Float writes float value to buffer. 13 | func (w *Writer) Float(v float64, bits int) bool { 14 | if math.IsNaN(v) || math.IsInf(v, 0) { 15 | // Like in ECMA: 16 | // NaN and Infinity regardless of sign are represented 17 | // as the String null. 18 | // 19 | // JSON.stringify({"foo":NaN}) -> {"foo":null} 20 | return w.Null() 21 | } 22 | 23 | switch s := w.stream; { 24 | case s == nil: 25 | w.Buf = floatAppend(w.Buf, v, bits) 26 | return false 27 | case s.fail(): 28 | return true 29 | default: 30 | tmp := make([]byte, 0, 32) 31 | tmp = floatAppend(tmp, v, bits) 32 | return writeStreamByteseq(w, tmp) 33 | } 34 | } 35 | 36 | func floatAppend(b []byte, v float64, bits int) []byte { 37 | // From go std sources, strconv/ftoa.go: 38 | 39 | // Convert as if by ES6 number to string conversion. 40 | // This matches most other JSON generators. 41 | // See golang.org/issue/6384 and golang.org/issue/14135. 42 | // Like fmt %g, but the exponent cutoffs are different 43 | // and exponents themselves are not padded to two digits. 44 | abs := math.Abs(v) 45 | fmt := byte('f') 46 | // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. 47 | if abs != 0 { 48 | if bits == 64 && (abs < 1e-6 || abs >= 1e21) || bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { 49 | fmt = 'e' 50 | } 51 | } 52 | b = strconv.AppendFloat(b, v, fmt, -1, bits) 53 | if fmt == 'e' { 54 | // clean up e-09 to e-9 55 | n := len(b) 56 | if n >= 4 && b[n-4] == 'e' && b[n-3] == '-' && b[n-2] == '0' { 57 | b[n-2] = b[n-1] 58 | b = b[:n-1] 59 | } 60 | } 61 | return b 62 | } 63 | -------------------------------------------------------------------------------- /w_int.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Int encodes int. 4 | func (w *Writer) Int(v int) bool { 5 | return w.Int64(int64(v)) 6 | } 7 | 8 | // UInt encodes uint. 9 | func (w *Writer) UInt(v uint) bool { 10 | return w.UInt64(uint64(v)) 11 | } 12 | 13 | // UInt8 encodes uint8. 14 | func (w *Writer) UInt8(v uint8) bool { 15 | // v is always smaller than digits size (1000) 16 | return writeFirstBuf(w, digits[v]) 17 | } 18 | 19 | // Int8 encodes int8. 20 | func (w *Writer) Int8(v int8) (fail bool) { 21 | var val uint8 22 | if v < 0 { 23 | val = uint8(-v) 24 | fail = w.byte('-') 25 | } else { 26 | val = uint8(v) 27 | } 28 | return fail || w.UInt8(val) 29 | } 30 | -------------------------------------------------------------------------------- /w_num.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | // Num encodes number. 4 | func (w *Writer) Num(v Num) bool { 5 | if len(v) == 0 { 6 | return w.Null() 7 | } 8 | return w.Raw(v) 9 | } 10 | -------------------------------------------------------------------------------- /w_str.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "github.com/go-faster/jx/internal/byteseq" 5 | ) 6 | 7 | const hexChars = "0123456789abcdef" 8 | 9 | // safeSet holds the value true if the ASCII character with the given array 10 | // position can be represented inside a JSON string without any further 11 | // escaping. 12 | // 13 | // All values are true except for the ASCII control characters (0-31), the 14 | // double quote ("), and the backslash character ("\"). 15 | var safeSet = [256]byte{ 16 | // First 31 characters. 17 | 1, 1, 1, 1, 1, 1, 1, 1, 18 | 1, 1, 1, 1, 1, 1, 1, 1, 19 | 1, 1, 1, 1, 1, 1, 1, 1, 20 | 1, 1, 1, 1, 1, 1, 1, 1, 21 | '"': 1, 22 | '\\': 1, 23 | } 24 | 25 | // Str encodes string without html escaping. 26 | // 27 | // Use StrEscape to escape html, this is default for encoding/json and 28 | // should be used by default for untrusted strings. 29 | func (w *Writer) Str(v string) bool { 30 | return writeStr(w, v) 31 | } 32 | 33 | // ByteStr encodes string without html escaping. 34 | // 35 | // Use ByteStrEscape to escape html, this is default for encoding/json and 36 | // should be used by default for untrusted strings. 37 | func (w *Writer) ByteStr(v []byte) bool { 38 | return writeStr(w, v) 39 | } 40 | 41 | func writeStr[S byteseq.Byteseq](w *Writer, v S) (fail bool) { 42 | fail = w.byte('"') 43 | 44 | // Fast path, without utf8 and escape support. 45 | var ( 46 | i = 0 47 | length = len(v) 48 | ) 49 | for ; i < length && !fail; i++ { 50 | c := v[i] 51 | if safeSet[c] != 0 { 52 | break 53 | } 54 | } 55 | fail = fail || writeStreamByteseq(w, v[:i]) 56 | if i == length { 57 | return fail || w.byte('"') 58 | } 59 | return fail || strSlow[S](w, v[i:]) 60 | } 61 | 62 | func strSlow[S byteseq.Byteseq](w *Writer, v S) (fail bool) { 63 | var i, start int 64 | // for the remaining parts, we process them char by char 65 | for i < len(v) && !fail { 66 | b := v[i] 67 | if safeSet[b] == 0 { 68 | i++ 69 | continue 70 | } 71 | if start < i { 72 | fail = fail || writeStreamByteseq(w, v[start:i]) 73 | } 74 | 75 | switch b { 76 | case '\\', '"': 77 | fail = fail || w.twoBytes('\\', b) 78 | case '\n': 79 | fail = fail || w.twoBytes('\\', 'n') 80 | case '\r': 81 | fail = fail || w.twoBytes('\\', 'r') 82 | case '\t': 83 | fail = fail || w.twoBytes('\\', 't') 84 | default: 85 | // This encodes bytes < 0x20 except for \t, \n and \r. 86 | // If escapeHTML is set, it also escapes <, >, and & 87 | // because they can lead to security holes when 88 | // user-controlled strings are rendered into JSON 89 | // and served to some browsers. 90 | fail = fail || w.rawStr(`\u00`) || w.twoBytes(hexChars[b>>4], hexChars[b&0xF]) 91 | } 92 | i++ 93 | start = i 94 | continue 95 | } 96 | if start < len(v) { 97 | fail = fail || writeStreamByteseq(w, v[start:]) 98 | } 99 | return fail || w.byte('"') 100 | } 101 | -------------------------------------------------------------------------------- /w_stream.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "github.com/go-faster/jx/internal/byteseq" 8 | ) 9 | 10 | // Close flushes underlying buffer to writer in streaming mode. 11 | // Otherwise, it does nothing. 12 | func (w *Writer) Close() error { 13 | if w.stream == nil { 14 | return nil 15 | } 16 | _, fail := w.stream.flush(w.Buf) 17 | if fail { 18 | return w.stream.writeErr 19 | } 20 | return nil 21 | } 22 | 23 | var errStreaming = errors.New("unexpected call in streaming mode") 24 | 25 | type streamState struct { 26 | writer io.Writer 27 | writeErr error 28 | } 29 | 30 | func newStreamState(w io.Writer) *streamState { 31 | return &streamState{ 32 | writer: w, 33 | } 34 | } 35 | 36 | func (s *streamState) mustNotBeStreaming() { 37 | if s != nil { 38 | panic(errStreaming) 39 | } 40 | } 41 | 42 | func (s *streamState) Reset(w io.Writer) { 43 | s.writer = w 44 | s.writeErr = nil 45 | } 46 | 47 | func (s *streamState) setError(err error) { 48 | s.writeErr = err 49 | } 50 | 51 | func (s *streamState) fail() bool { 52 | return s.writeErr != nil 53 | } 54 | 55 | func (s *streamState) flush(buf []byte) ([]byte, bool) { 56 | if s.fail() { 57 | return nil, true 58 | } 59 | 60 | n, err := s.writer.Write(buf) 61 | switch { 62 | case err != nil: 63 | s.setError(err) 64 | return nil, true 65 | case n != len(buf): 66 | s.setError(io.ErrShortWrite) 67 | return nil, true 68 | default: 69 | buf = buf[:0] 70 | return buf, false 71 | } 72 | } 73 | 74 | func writeStreamBytes(w *Writer, s ...byte) bool { 75 | return writeStreamByteseq(w, s) 76 | } 77 | 78 | func writeStreamByteseq[S byteseq.Byteseq](w *Writer, s S) bool { 79 | if w.stream == nil { 80 | w.Buf = append(w.Buf, s...) 81 | return false 82 | } 83 | return writeStreamByteseqSlow(w, s) 84 | } 85 | 86 | func writeStreamByteseqSlow[S byteseq.Byteseq](w *Writer, s S) bool { 87 | if w.stream.fail() { 88 | return true 89 | } 90 | 91 | for len(w.Buf)+len(s) > cap(w.Buf) { 92 | var fail bool 93 | w.Buf, fail = w.stream.flush(w.Buf) 94 | if fail { 95 | return true 96 | } 97 | 98 | n := copy(w.Buf[len(w.Buf):cap(w.Buf)], s) 99 | s = s[n:] 100 | w.Buf = w.Buf[:len(w.Buf)+n] 101 | } 102 | w.Buf = append(w.Buf, s...) 103 | return false 104 | } 105 | -------------------------------------------------------------------------------- /w_test.go: -------------------------------------------------------------------------------- 1 | package jx 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestWriter_Reset(t *testing.T) { 10 | w := GetWriter() 11 | defer PutWriter(w) 12 | 13 | w.True() 14 | require.NotEmpty(t, w.Buf) 15 | w.Reset() 16 | require.Empty(t, w.Buf) 17 | } 18 | 19 | func TestWriter_String(t *testing.T) { 20 | w := GetWriter() 21 | defer PutWriter(w) 22 | 23 | w.True() 24 | require.Equal(t, "true", w.String()) 25 | } 26 | 27 | func TestWriter_Grow(t *testing.T) { 28 | should := require.New(t) 29 | e := &Writer{} 30 | should.Equal(0, len(e.Buf)) 31 | should.Equal(0, cap(e.Buf)) 32 | e.Grow(1024) 33 | should.Equal(0, len(e.Buf)) 34 | should.Equal(1024, cap(e.Buf)) 35 | e.Grow(512) 36 | should.Equal(0, len(e.Buf)) 37 | should.Equal(1024, cap(e.Buf)) 38 | e.Grow(4096) 39 | should.Equal(0, len(e.Buf)) 40 | should.Equal(4096, cap(e.Buf)) 41 | } 42 | --------------------------------------------------------------------------------