Просмотр исходного кода

Merge branch 'master' into hipe

Takeru Ohta 10 лет назад
Родитель
Сommit
7be49c9d04
6 измененных файлов с 190 добавлено и 95 удалено
  1. 3 6
      README.md
  2. 41 1
      doc/jsone.md
  3. 7 7
      rebar.config
  4. 31 13
      src/jsone.erl
  5. 103 67
      src/jsone_encode.erl
  6. 5 1
      test/jsone_encode_tests.erl

+ 3 - 6
README.md

@@ -1,4 +1,4 @@
-jsone (0.2.3)
+jsone (0.2.4)
 =============
 
 An Erlang library for encoding, decoding [JSON](http://json.org/index.html) data.
@@ -140,7 +140,7 @@ Benchmark
 - row: data size (bytes)
 - cell: elapsed time (micro seconds)
 
-|                   | jiffy | jsone | jsonerl | jsonx |  jsx  | mochijson2 | yawsjson2 | 
+|                   | jiffy | jsone | jsonerl | jsonx |  jsx  | mochijson2 | yawsjson2 |
 |------------------:|------:|------:|--------:|------:|------:|-----------:|----------:|
 | 559 (1x)          | 12    | 11    | 61      | 7     | 50    | 28         | 37        |
 | 1583 (3x)         | 24    | 25    | 66      | 15    | 134   | 68         | 84        |
@@ -155,7 +155,7 @@ Benchmark
 - row: data size (bytes)
 - cell: elapsed time (micro seconds)
 
-|                   | jiffy | jsone | jsonerl | jsonx |  jsx  | mochijson2 | yawsjson2 | 
+|                   | jiffy | jsone | jsonerl | jsonx |  jsx  | mochijson2 | yawsjson2 |
 |------------------:|------:|------:|--------:|------:|------:|-----------:|----------:|
 | 559 (1x)          | 14    | 19    | 21      | 8     | 83    | 19         | 15        |
 | 1583 (3x)         | 29    | 49    | 65      | 14    | 228   | 61         | 42        |
@@ -163,6 +163,3 @@ Benchmark
 | 13914 (27x)       | 215   | 393   | 737     | 101   | 1993  | 664        | 435       |
 | 41542 (81x)       | 621   | 1172  | 2058    | 300   | 6237  | 2310       | 1192      |
 | 124726 (243x)     | 1830  | 3968  | 5842    | 828   | 17032 | 6979       | 5266      |
-
-
-

+ 41 - 1
doc/jsone.md

@@ -17,6 +17,20 @@ JSON decoding/encoding module.
 
 
 
+### <a name="type-encode_option">encode_option()</a> ###
+
+
+
+<pre><code>
+encode_option() = native_utf8
+</code></pre>
+
+
+
+  native_utf8: Encodes UTF-8 characters as a human-readable(non-escaped) string
+
+
+
 ### <a name="type-json_array">json_array()</a> ###
 
 
@@ -103,7 +117,7 @@ json_value() = <a href="#type-json_number">json_number()</a> | <a href="#type-js
 ## Function Index ##
 
 
-<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#decode-1">decode/1</a></td><td>Decodes an erlang term from json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#encode-1">encode/1</a></td><td>Encodes an erlang term into json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#try_decode-1">try_decode/1</a></td><td>Decodes an erlang term from json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#try_encode-1">try_encode/1</a></td><td>Encodes an erlang term into json text (a utf8 encoded binary).</td></tr></table>
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#decode-1">decode/1</a></td><td>Decodes an erlang term from json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#encode-1">encode/1</a></td><td>Equivalent to <a href="#encode-2"><tt>encode(JsonValue, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#encode-2">encode/2</a></td><td>Encodes an erlang term into json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#try_decode-1">try_decode/1</a></td><td>Decodes an erlang term from json text (a utf8 encoded binary).</td></tr><tr><td valign="top"><a href="#try_encode-1">try_encode/1</a></td><td>Equivalent to <a href="#try_encode-2"><tt>try_encode(JsonValue, [])</tt></a>.</td></tr><tr><td valign="top"><a href="#try_encode-2">try_encode/2</a></td><td>Encodes an erlang term into json text (a utf8 encoded binary).</td></tr></table>
 
 
 <a name="functions"></a>
@@ -153,6 +167,19 @@ encode(JsonValue::<a href="#type-json_value">json_value()</a>) -&gt; binary()
 <br></br>
 
 
+Equivalent to [`encode(JsonValue, [])`](#encode-2).
+<a name="encode-2"></a>
+
+### encode/2 ###
+
+
+<pre><code>
+encode(JsonValue::<a href="#type-json_value">json_value()</a>, Options::[<a href="#type-encode_option">encode_option()</a>]) -&gt; binary()
+</code></pre>
+
+<br></br>
+
+
 
 Encodes an erlang term into json text (a utf8 encoded binary)
 
@@ -210,6 +237,19 @@ try_encode(JsonValue::<a href="#type-json_value">json_value()</a>) -&gt; {ok, bi
 <br></br>
 
 
+Equivalent to [`try_encode(JsonValue, [])`](#try_encode-2).
+<a name="try_encode-2"></a>
+
+### try_encode/2 ###
+
+
+<pre><code>
+try_encode(JsonValue::<a href="#type-json_value">json_value()</a>, Options::[<a href="#type-encode_option">encode_option()</a>]) -&gt; {ok, binary()} | {error, {Reason::term(), [<a href="erlang.md#type-stack_item">erlang:stack_item()</a>]}}
+</code></pre>
+
+<br></br>
+
+
 
 Encodes an erlang term into json text (a utf8 encoded binary)
 

+ 7 - 7
rebar.config

@@ -4,28 +4,28 @@
             warn_export_all,
             warn_untyped_record,
             inline
-           ]}. 
+           ]}.
 
 {xref_checks, [
                fail_on_warning,
                undefined_function_calls
               ]}.
- 
+
 {clean_files, [".eunit/*", "ebin/*.beam"]}.
- 
+
 {cover_enabled, true}.
- 
+
 {edoc_opts, [
              {doclet, edown_doclet},
              {dialyzer_specs, all},
-             {report_missing_type, true}, 
+             {report_missing_type, true},
              {report_type_mismatch, true},
              {pretty_print, erl_pp},
              {preprocess, true}
             ]}.
 {validate_app_modules, true}.
- 
+
 {deps,
   [
-%%   {edown, ".*", {git, "git://github.com/sile/edown.git", {branch, "master"}}}
+   %% {edown, ".*", {git, "git://github.com/sile/edown.git", {branch, "master"}}}
   ]}.

+ 31 - 13
src/jsone.erl

@@ -31,7 +31,8 @@
 %%--------------------------------------------------------------------------------
 -export([
          decode/1, try_decode/1,
-         encode/1, try_encode/1
+         encode/1, encode/2,
+         try_encode/1, try_encode/2
         ]).
 
 -export_type([
@@ -41,11 +42,13 @@
               json_array/0,
               json_object/0,
               json_object_members/0,
-              json_boolean/0
+              json_boolean/0,
+
+              encode_option/0
              ]).
 
 %%--------------------------------------------------------------------------------
-%% Types
+%% Types & Macros
 %%--------------------------------------------------------------------------------
 -type json_value()          :: json_number() | json_string() | json_array() | json_object() | json_boolean() | null.
 -type json_boolean()        :: boolean().
@@ -55,6 +58,11 @@
 -type json_object()         :: {json_object_members()}.
 -type json_object_members() :: [{json_string(), json_value()}].
 
+-type encode_option() :: native_utf8.
+%% native_utf8: Encodes UTF-8 characters as a human-readable(non-escaped) string
+
+-define(DEFAULT_ENCODE_OPTIONS, []).
+
 %%--------------------------------------------------------------------------------
 %% Exported Functions
 %%--------------------------------------------------------------------------------
@@ -65,7 +73,7 @@
 %% ```
 %% > jsone:decode(<<"1">>).
 %% 1
-%% 
+%%
 %% > jsone:decode(<<"wrong json">>).
 %% ** exception error: bad argument
 %%     in function  jsone_decode:number_integer_part/4
@@ -87,7 +95,7 @@ decode(Json) ->
 %% ```
 %% > jsone:try_decode(<<"[1,2,3] \"next value\"">>).
 %% {ok,[1,2,3],<<" \"next value\"">>}
-%%  
+%%
 %% > jsone:try_decode(<<"wrong json">>).
 %% {error,{badarg,[{jsone_decode,number_integer_part,
 %%                               [<<"wrong json">>,1,[],<<>>],
@@ -97,6 +105,11 @@ decode(Json) ->
 try_decode(Json) ->
     jsone_decode:decode(Json).
 
+%% @equiv encode(JsonValue, [])
+-spec encode(json_value()) -> binary().
+encode(JsonValue) ->
+    encode(JsonValue, ?DEFAULT_ENCODE_OPTIONS).
+
 %% @doc Encodes an erlang term into json text (a utf8 encoded binary)
 %%
 %% Raises an error exception if input is not an instance of type `json_value()'
@@ -104,34 +117,39 @@ try_decode(Json) ->
 %% ```
 %% > jsone:encode([1, null, 2]).
 %% <<"[1,null,2]">>
-%%  
+%%
 %% > jsone:encode([1, hoge, 2]).  % 'hoge' atom is not a json value
 %% ** exception error: bad argument
 %%      in function  jsone_encode:value/3
 %%         called as jsone_encode:value(hoge,[{array_values,[2]}],<<"[1,">>)
 %%      in call from jsone:encode/1 (src/jsone.erl, line 97)
 %% '''
--spec encode(json_value()) -> binary().
-encode(JsonValue) ->
+-spec encode(json_value(), [encode_option()]) -> binary().
+encode(JsonValue, Options) ->
     try
-        {ok, Binary} = try_encode(JsonValue),
+        {ok, Binary} = try_encode(JsonValue, Options),
         Binary
     catch
         error:{badmatch, {error, {Reason, [StackItem]}}} ->
             erlang:raise(error, Reason, [StackItem | erlang:get_stacktrace()])
     end.
 
+%% @equiv try_encode(JsonValue, [])
+-spec try_encode(json_value()) -> {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
+try_encode(JsonValue) ->
+    jsone_encode:encode(JsonValue, ?DEFAULT_ENCODE_OPTIONS).
+
 %% @doc Encodes an erlang term into json text (a utf8 encoded binary)
 %%
 %% ```
 %% > jsone:try_encode([1, null, 2]).
 %% {ok,<<"[1,null,2]">>}
-%%  
+%%
 %% > jsone:try_encode([1, hoge, 2]).  % 'hoge' atom is not a json value
 %% {error,{badarg,[{jsone_encode,value,
 %%                               [hoge,[{array_values,[2]}],<<"[1,">>],
 %%                               [{line,86}]}]}}
 %% '''
--spec try_encode(json_value()) -> {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
-try_encode(JsonValue) ->
-    jsone_encode:encode(JsonValue).
+-spec try_encode(json_value(), [encode_option()]) -> {ok, binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
+try_encode(JsonValue, Options) ->
+    jsone_encode:encode(JsonValue, Options).

+ 103 - 67
src/jsone_encode.erl

@@ -32,7 +32,7 @@
 %%--------------------------------------------------------------------------------
 %% Exported API
 %%--------------------------------------------------------------------------------
--export([encode/1]).
+-export([encode/1, encode/2]).
 
 %%--------------------------------------------------------------------------------
 %% Macros & Types
@@ -47,98 +47,134 @@
               | {object_value, jsone:json_value(), jsone:json_object_members()}
               | {object_members, jsone:json_object_members()}.
 
+-record(encode_opt_v1, { native_utf8 = false :: boolean() }).
+-define(ENCODE_OPT, #encode_opt_v1).
+-type encode_opt() :: #encode_opt_v1{}.
+
 %%--------------------------------------------------------------------------------
 %% Exported Functions
 %%--------------------------------------------------------------------------------
 %% @doc Encodes an erlang term into json text (a utf8 encoded binary)
 -spec encode(jsone:json_value()) -> encode_result().
 encode(Value) ->
-    value(Value, [], <<"">>).
+    jsone:try_encode(Value).
+
+-spec encode(jsone:json_value(), [jsone:encode_option()]) -> encode_result().
+encode(Value, Options) ->
+    Opt = parse_options(Options),
+    value(Value, [], <<"">>, Opt).
 
 %%--------------------------------------------------------------------------------
 %% Internal Functions
 %%--------------------------------------------------------------------------------
--spec next([next()], binary()) -> encode_result().
-next([], Buf)             -> {ok, Buf};
-next([Next | Nexts], Buf) ->
+-spec next([next()], binary(), encode_opt()) -> encode_result().
+next([], Buf, _)             -> {ok, Buf};
+next([Next | Nexts], Buf, Opt) ->
     case Next of
         {array_values, Values} ->
             case Values of
-                [] -> array_values(Values, Nexts, Buf);
-                _  -> array_values(Values, Nexts, <<Buf/binary, $,>>)
+                [] -> array_values(Values, Nexts, Buf, Opt);
+                _  -> array_values(Values, Nexts, <<Buf/binary, $,>>, Opt)
             end;
         {object_value, Value, Members} ->
-            object_value(Value, Members, Nexts, Buf);
+            object_value(Value, Members, Nexts, Buf, Opt);
         {object_members, Members} ->
             case Members of
-                [] -> object_members(Members, Nexts, Buf);
-                _  -> object_members(Members, Nexts, <<Buf/binary, $,>>)
+                [] -> object_members(Members, Nexts, Buf, Opt);
+                _  -> object_members(Members, Nexts, <<Buf/binary, $,>>, Opt)
             end
     end.
 
--spec value(jsone:json_value(), [next()], binary()) -> encode_result().
-value(null, Nexts, Buf)                         -> next(Nexts, <<Buf/binary, "null">>);
-value(false, Nexts, Buf)                        -> next(Nexts, <<Buf/binary, "false">>);
-value(true, Nexts, Buf)                         -> next(Nexts, <<Buf/binary, "true">>);
-value(Value, Nexts, Buf) when is_integer(Value) -> next(Nexts, <<Buf/binary, (integer_to_binary(Value))/binary>>);
-value(Value, Nexts, Buf) when is_float(Value)   -> next(Nexts, <<Buf/binary, (float_to_binary(Value))/binary>>);
-value(Value, Nexts, Buf) when is_binary(Value)  -> string(Value, Nexts, Buf);
-value(Value, Nexts, Buf) when is_list(Value)    -> array(Value, Nexts, Buf);
-value({_} = Value, Nexts, Buf)                  -> object(Value, Nexts, Buf);
-value(Value, Nexts, Buf)                        -> ?ERROR(value, [Value, Nexts, Buf]).
-
--spec string(jsone:json_string(), [next()], binary()) -> encode_result().
-string(<<Str/binary>>, Nexts, Buf) ->
-    escape_string(Str, Nexts, <<Buf/binary, $">>).
-
--spec escape_string(binary(), [next()], binary()) -> encode_result().
-escape_string(<<"">>,                   Nexts, Buf) -> next(Nexts, <<Buf/binary, $">>);
-escape_string(<<$", Str/binary>>,       Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $">>);
-escape_string(<<$\/, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $\/>>);
-escape_string(<<$\\, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $\\>>);
-escape_string(<<$\b, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $b>>);
-escape_string(<<$\f, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $f>>);
-escape_string(<<$\n, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $n>>);
-escape_string(<<$\r, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $r>>);
-escape_string(<<$\t, Str/binary>>,      Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $t>>);
-escape_string(<<0:1, C:7, Str/binary>>, Nexts, Buf) -> escape_string(Str, Nexts, <<Buf/binary, C>>);
-escape_string(<<2#110:3, B1:5, 2#10:2, B2:6, Str/binary>>, Nexts, Buf) when not ?IS_REDUNDANT_UTF8(B1, B2, 5) ->
-    Unicode = (B1 bsl 6) + B2,
-    escape_unicode_char(Str, Unicode, Nexts, Buf);
-escape_string(<<2#1110:4, B1:4, 2#10:2, B2:6, 2#10:2, B3:6, Str/binary>>, Nexts, Buf) when not ?IS_REDUNDANT_UTF8(B1, B2, 4) ->
-    Unicode = (B1 bsl 12) + (B2 bsl 6) + B3,
-    escape_unicode_char(Str, Unicode, Nexts, Buf);
-escape_string(<<2#11110:5, B1:3, 2#10:2, B2:6, 2#10:2, B3:6, 2#10:2, B4:6, Str/binary>>, Nexts, Buf) when not ?IS_REDUNDANT_UTF8(B1, B2, 3) ->
-    Unicode = (B1 bsl 18) + (B2 bsl 12) + (B3 bsl 6) + B4,
-    escape_unicode_char(Str, Unicode, Nexts, Buf);
-escape_string(Str, Nexts, Buf) ->
+-spec value(jsone:json_value(), [next()], binary(), encode_opt()) -> encode_result().
+value(null, Nexts, Buf, Opt)                         -> next(Nexts, <<Buf/binary, "null">>, Opt);
+value(false, Nexts, Buf, Opt)                        -> next(Nexts, <<Buf/binary, "false">>, Opt);
+value(true, Nexts, Buf, Opt)                         -> next(Nexts, <<Buf/binary, "true">>, Opt);
+value(Value, Nexts, Buf, Opt) when is_integer(Value) -> next(Nexts, <<Buf/binary, (integer_to_binary(Value))/binary>>, Opt);
+value(Value, Nexts, Buf, Opt) when is_float(Value)   -> next(Nexts, <<Buf/binary, (float_to_binary(Value))/binary>>, Opt);
+value(Value, Nexts, Buf, Opt) when is_binary(Value)  -> string(Value, Nexts, Buf, Opt);
+value(Value, Nexts, Buf, Opt) when is_list(Value)    -> array(Value, Nexts, Buf, Opt);
+value({_} = Value, Nexts, Buf, Opt)                  -> object(Value, Nexts, Buf, Opt);
+value(Value, Nexts, Buf, _)                        -> ?ERROR(value, [Value, Nexts, Buf]).
+
+-spec string(jsone:json_string(), [next()], binary(), encode_opt()) -> encode_result().
+string(<<Str/binary>>, Nexts, Buf, Opt) ->
+    escape_string(Str, Nexts, <<Buf/binary, $">>, Opt).
+
+-spec escape_string(binary(), [next()], binary(), encode_opt()) -> encode_result().
+escape_string(<<"">>,                   Nexts, Buf, Opt) -> next(Nexts, <<Buf/binary, $">>, Opt);
+escape_string(<<$", Str/binary>>,       Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $">>, Opt);
+escape_string(<<$\/, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $\/>>, Opt);
+escape_string(<<$\\, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $\\>>, Opt);
+escape_string(<<$\b, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $b>>, Opt);
+escape_string(<<$\f, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $f>>, Opt);
+escape_string(<<$\n, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $n>>, Opt);
+escape_string(<<$\r, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $r>>, Opt);
+escape_string(<<$\t, Str/binary>>,      Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, $\\, $t>>, Opt);
+escape_string(<<0:1, C:7, Str/binary>>, Nexts, Buf, Opt) -> escape_string(Str, Nexts, <<Buf/binary, C>>, Opt);
+escape_string(<<2#110:3, B1:5, 2#10:2, B2:6, Str/binary>>, Nexts, Buf, Opt) when not ?IS_REDUNDANT_UTF8(B1, B2, 5) ->
+    case Opt?ENCODE_OPT.native_utf8 of
+        false ->
+            Unicode = (B1 bsl 6) + B2,
+            escape_unicode_char(Str, Unicode, Nexts, Buf, Opt);
+        true ->
+            unicode_char(Str, <<2#110:3, B1:5, 2#10:2, B2:6>>, Nexts, Buf, Opt)
+    end;
+escape_string(<<2#1110:4, B1:4, 2#10:2, B2:6, 2#10:2, B3:6, Str/binary>>, Nexts, Buf, Opt) when not ?IS_REDUNDANT_UTF8(B1, B2, 4) ->
+    case Opt?ENCODE_OPT.native_utf8 of
+        false ->
+            Unicode = (B1 bsl 12) + (B2 bsl 6) + B3,
+            escape_unicode_char(Str, Unicode, Nexts, Buf, Opt);
+        true ->
+            unicode_char(Str, <<2#1110:4, B1:4, 2#10:2, B2:6, 2#10:2, B3:6>>, Nexts, Buf, Opt)
+    end;
+escape_string(<<2#11110:5, B1:3, 2#10:2, B2:6, 2#10:2, B3:6, 2#10:2, B4:6, Str/binary>>, Nexts, Buf, Opt) when not ?IS_REDUNDANT_UTF8(B1, B2, 3) ->
+    case Opt?ENCODE_OPT.native_utf8 of
+        false ->
+            Unicode = (B1 bsl 18) + (B2 bsl 12) + (B3 bsl 6) + B4,
+            escape_unicode_char(Str, Unicode, Nexts, Buf, Opt);
+        true ->
+            unicode_char(Str, <<2#11000:5, B1:3, 2#10:2, B2:6, 2#10:2, B3:6, 2#10:2, B4:6>>, Nexts, Buf, Opt)
+    end;
+escape_string(Str, Nexts, Buf, _) ->
     ?ERROR(escape_string, [Str, Nexts, Buf]).
 
--spec escape_unicode_char(binary(), char(), [next()], binary()) -> encode_result().
-escape_unicode_char(<<Str/binary>>, Unicode, Nexts, Buf) when Unicode =< 16#FFFF ->
-    escape_string(Str, Nexts, <<Buf/binary, $\\, $u, ?UNICODE_TO_HEX(Unicode)>>);
-escape_unicode_char(<<Str/binary>>, Unicode, Nexts, Buf) ->
+unicode_char(Str, Char, Nexts, Buf, Opt) ->
+    escape_string(Str, Nexts, <<Buf/binary, Char/binary>>, Opt).
+
+-spec escape_unicode_char(binary(), char(), [next()], binary(), encode_opt()) -> encode_result().
+escape_unicode_char(<<Str/binary>>, Unicode, Nexts, Buf, Opt) when Unicode =< 16#FFFF ->
+    escape_string(Str, Nexts, <<Buf/binary, $\\, $u, ?UNICODE_TO_HEX(Unicode)>>, Opt);
+escape_unicode_char(<<Str/binary>>, Unicode, Nexts, Buf, Opt) ->
     %% Surrogate Pair
     <<High:10, Low:10>> = <<(Unicode - 16#10000):20>>, % XXX: inefficient
-    escape_string(Str, Nexts, <<Buf/binary, $\\, $u, ?UNICODE_TO_HEX(High + 16#D800), $\\, $u, ?UNICODE_TO_HEX(Low + 16#DC00)>>).
+    escape_string(Str, Nexts, <<Buf/binary, $\\, $u, ?UNICODE_TO_HEX(High + 16#D800), $\\, $u, ?UNICODE_TO_HEX(Low + 16#DC00)>>, Opt).
+
+-spec array(jsone:json_array(), [next()], binary(), encode_opt()) -> encode_result().
+array(List, Nexts, Buf, Opt) ->
+    array_values(List, Nexts, <<Buf/binary, $[>>, Opt).
+
+-spec array_values(jsone:json_array(), [next()], binary(), encode_opt()) -> encode_result().
+array_values([],       Nexts, Buf, Opt) -> next(Nexts, <<Buf/binary, $]>>, Opt);
+array_values([X | Xs], Nexts, Buf, Opt) -> value(X, [{array_values, Xs} | Nexts], Buf, Opt).
+
+-spec object(jsone:json_object(), [next()], binary(), encode_opt()) -> encode_result().
+object({Members}, Nexts, Buf, Opt) ->
+    object_members(Members, Nexts, <<Buf/binary, ${>>, Opt).
 
--spec array(jsone:json_array(), [next()], binary()) -> encode_result().
-array(List, Nexts, Buf) ->
-    array_values(List, Nexts, <<Buf/binary, $[>>).
+-spec object_members(jsone:json_object_members(), [next()], binary(), encode_opt()) -> encode_result().
+object_members([],                             Nexts, Buf, Opt) -> next(Nexts, <<Buf/binary, $}>>, Opt);
+object_members([{<<Key/binary>>, Value} | Xs], Nexts, Buf, Opt) -> string(Key, [{object_value, Value, Xs} | Nexts], Buf, Opt);
+object_members(Arg, Nexts, Buf, _)                            -> ?ERROR(object_members, [Arg, Nexts, Buf]).
 
--spec array_values(jsone:json_array(), [next()], binary()) -> encode_result().
-array_values([],       Nexts, Buf) -> next(Nexts, <<Buf/binary, $]>>);
-array_values([X | Xs], Nexts, Buf) -> value(X, [{array_values, Xs} | Nexts], Buf).
+-spec object_value(jsone:json_value(), jsone:json_object_members(), [next()], binary(), encode_opt()) -> encode_result().
+object_value(Value, Members, Nexts, Buf, Opt) ->
+    value(Value, [{object_members, Members} | Nexts], <<Buf/binary, $:>>, Opt).
 
--spec object(jsone:json_object(), [next()], binary()) -> encode_result().
-object({Members}, Nexts, Buf) ->
-    object_members(Members, Nexts, <<Buf/binary, ${>>).
 
--spec object_members(jsone:json_object_members(), [next()], binary()) -> encode_result().
-object_members([],                             Nexts, Buf) -> next(Nexts, <<Buf/binary, $}>>);
-object_members([{<<Key/binary>>, Value} | Xs], Nexts, Buf) -> string(Key, [{object_value, Value, Xs} | Nexts], Buf);
-object_members(Arg, Nexts, Buf)                            -> ?ERROR(object_members, [Arg, Nexts, Buf]).
+-spec parse_options([jsone:encode_option()]) -> encode_opt().
+parse_options(Options) ->
+    parse_option(Options, ?ENCODE_OPT{}).
 
--spec object_value(jsone:json_value(), jsone:json_object_members(), [next()], binary()) -> encode_result().
-object_value(Value, Members, Nexts, Buf) ->
-    value(Value, [{object_members, Members} | Nexts], <<Buf/binary, $:>>).
+parse_option([], Opt) -> Opt;
+parse_option([native_utf8|T], Opt) ->
+    parse_option(T, Opt?ENCODE_OPT{native_utf8=true}).

+ 5 - 1
test/jsone_encode_tests.erl

@@ -64,11 +64,15 @@ encode_test_() ->
               Input1    = <<"あいうえお">>,  % assumed that the encoding of this file is UTF-8
               Expected1 = <<"\"\\u3042\\u3044\\u3046\\u3048\\u304a\"">>,
               ?assertEqual({ok, Expected1}, jsone_encode:encode(Input1)),
+              Expected12 = <<$", Input1/binary, $">>,
+              ?assertEqual({ok, Expected12}, jsone_encode:encode(Input1, [native_utf8])),
 
               %% other multi-byte characters
               Input2    = <<"۝۞ႮႯ">>,
               Expected2 = <<"\"\\u06dd\\u06de\\u10ae\\u10af\"">>,
-              ?assertEqual({ok, Expected2}, jsone_encode:encode(Input2))
+              ?assertEqual({ok, Expected2}, jsone_encode:encode(Input2)),
+              Expected22 = <<$", Input2/binary, $">>,
+              ?assertEqual({ok, Expected22}, jsone_encode:encode(Input2, [native_utf8]))
       end},
      {"string: containts surrogate pairs",
       fun () ->