anchor.diff:  -export([ decode_schema/1
           , decode_schema/2
           , decode_value/3
  -        , decode_value_jsonx/3
  +        , decode_value/4
           ]).
   
   -include("erlavro.hrl").
  +
  +-type options() :: [{atom(), term()}].
   
   %%%===================================================================
   %%% API
  
positive.code: parse_bytes(_, _) ->
    erlang:error(wrong_bytes_string).
  
  parse_record({struct, Attrs}, Type, ExtractFun) ->
    Fields = convert_attrs_to_record_fields(Attrs, Type, ExtractFun),
    avro_record:new(Type, Fields);
  parse_record(_, _, _) ->
    erlang:error(wrong_record_value).
  
  convert_attrs_to_record_fields(Attrs, Type, ExtractFun) ->
    lists:map(
      fun({FieldNameBin, Value}) ->
          FieldName = binary_to_list(FieldNameBin),
          FieldType = avro_record:get_field_type(FieldName, Type),
          {FieldName, parse_value(Value, FieldType, ExtractFun)}
      end,
      Attrs).
  
  parse_array(V, Type, ExtractFun) when is_list(V) ->
    ItemsType = avro_array:get_items_type(Type),
    Items = lists:map(
              fun(Item) ->
                  parse_value(Item, ItemsType, ExtractFun)
              end,
              V),
    %% Here we can use direct version of new because we casted all items
    %% to the array type before
    avro_array:new_direct(Type, Items);
  parse_array(_, _, _) ->
    erlang:error(wrong_array_value).
  
  parse_map({struct, Attrs}, Type, ExtractFun) ->
    ItemsType = avro_map:get_items_type(Type),
    D = lists:foldl(
          fun({KeyBin, Value}, D) ->
              dict:store(binary_to_list(KeyBin),
                         parse_value(Value, ItemsType, ExtractFun),
                         D)
          end,
          dict:new(),
          Attrs),
    avro_map:new(Type, D).
  
  parse_union(null = Value, Type, ExtractFun) ->
    %% Union values specified as null
    parse_union_ex(?AVRO_NULL, Value, Type, ExtractFun);
  parse_union({struct, [{ValueTypeNameBin, Value}]}, Type, ExtractFun) ->
    %% Union value specified as {"type": <value>}
    ValueTypeName = binary_to_list(ValueTypeNameBin),
    parse_union_ex(ValueTypeName, Value, Type, ExtractFun);
  parse_union(_, _, _) ->
    erlang:error(wrong_union_value).
  
  parse_union_ex(ValueTypeName, Value, UnionType, ExtractFun) ->
    case avro_union:lookup_child_type(UnionType, ValueTypeName) of
      {ok, ValueType} ->
        %% Here we can create the value directly because we know that
        %% the type of value belongs to the union type and we can skip
        %% additional looping over union types in avro_union:cast
        avro_union:new_direct(UnionType,
                              parse_value(Value, ValueType, ExtractFun));
      false ->
        erlang:error(unknown_type_of_union_value)
    end.
  
  parse_fixed(V, Type, _ExtractFun) ->
    avro_fixed:new(Type, parse_bytes(V)).
  
  %%%===================================================================
  %%% Tests
  
negative.code: %% without problems.
  %% Current strategy is to use jsonx as the main parser and fall back to
  %% mochijson3 in case of parsing issues.
  -spec decode_value(binary(), avro_type_or_name(),
                     fun((string()) -> avro_type()), options()) ->
                        avro_value() | term().
  decode_value(JsonValue, Schema, ExtractTypeFun, Options) ->
    DecodedJson =
      case lists:keyfind(decoder, Options) of
        {_, jsonx} ->
          case jsonx:decode(JsonValue, [{format, struct}]) of
            {error, _Err, _Pos} ->
              mochijson3:decode(JsonValue);
            Decoded ->
              Decoded
          end;
        _ ->
          mochijson3:decode(JsonValue)
      end,
    IsWrapped = case lists:keyfind(is_wrapped, Options) of
                  {is_wrapped, V} -> V;
                  false           -> true %% parse to wrapped value by default
                end,
    parse(DecodedJson, Schema, ExtractTypeFun, IsWrapped).
  
  %% Decode value specified as Json string according to Avro schema
  %% in Schema. ExtractTypeFun should be provided to retrieve types
  %% specified by their names inside Schema.
  -spec decode_value(binary(),
                     avro_type_or_name(),
                     fun((string()) -> avro_type()))
                    -> avro_value().
  decode_value(JsonValue, Schema, ExtractTypeFun) ->
    decode_value(JsonValue, Schema, ExtractTypeFun, [{decoder, mochijson3}]).
  
  %%%===================================================================
  %%% Schema parsing
  