@@ -499,50 +499,234 @@ defmodule JSON do
499499 end
500500 end
501501
502+ @ max_indent 31
503+
504+ @ indent_specials [ ?{ , ?} , ?[ , ?] , ?, , ?: , ?" , ?\s , ?\t , ?\r , ?\n ]
505+
502506 @ doc ~S"""
503507 Encodes the given term to JSON as a binary.
504508
505- The second argument is a function that is recursively
506- invoked to encode a term.
509+ The second argument is either a function that is recursively invoked to
510+ encode a term, or a keyword list of options, described below .
507511
508512 > #### IO and performance {: .tip}
509513 >
510514 > If you need to encode data to be sent over the network
511515 > or written to the filesystem, consider using the more
512516 > efficient `encode_to_iodata!/2`.
513517
518+ ## Options
519+
520+ When the second argument is a keyword list, the only accepted option is:
521+
522+ * `:pretty` - either `true` to indent nested members by two spaces,
523+ or a binary of spaces and tabs to be used as the indent unit.
524+ Empty containers are kept on a single line. Defaults to `false`.
525+
526+ Unknown options raise. A custom encoder function cannot be combined
527+ with these options.
528+
514529 ## Examples
515530
516531 iex> JSON.encode!([123, "string", %{key: "value"}])
517532 "[123,\" string\" ,{\" key\" :\" value\" }]"
518533
534+ iex> JSON.encode!(%{tags: []}, pretty: true)
535+ "{\n \" tags\" : []\n}"
536+
537+ iex> JSON.encode!(%{id: "abc"}, pretty: "\t")
538+ "{\n\t\" id\" : \" abc\" \n}"
539+
519540 """
520- @ spec encode! ( term ( ) , encoder ( ) ) :: binary ( )
521- def encode! ( term , encoder \\ & protocol_encode / 2 ) do
541+ @ spec encode! ( term ( ) , encoder ( ) | [ pretty: boolean ( ) | binary ( ) ] ) :: binary ( )
542+ def encode! ( term , encoder_or_opts \\ & protocol_encode / 2 )
543+
544+ def encode! ( term , encoder ) when is_function ( encoder , 2 ) do
522545 IO . iodata_to_binary ( encoder . ( term , encoder ) )
523546 end
524547
548+ def encode! ( term , opts ) when is_list ( opts ) do
549+ term |> encode_to_iodata! ( opts ) |> IO . iodata_to_binary ( )
550+ end
551+
525552 @ doc ~S"""
526553 Encodes the given term to JSON as an iodata.
527554
528555 This is the most efficient format if the JSON is going to be
529556 used for IO purposes.
530557
531558 The second argument is a function that is recursively
532- invoked to encode a term.
559+ invoked to encode a term. It may also be a keyword list of
560+ options, as accepted by `encode!/2`.
533561
534562 ## Examples
535563
536564 iex> data = JSON.encode_to_iodata!([123, "string", %{key: "value"}])
537565 iex> IO.iodata_to_binary(data)
538566 "[123,\" string\" ,{\" key\" :\" value\" }]"
539567
568+ iex> data = JSON.encode_to_iodata!(%{id: "abc"}, pretty: true)
569+ iex> IO.iodata_to_binary(data)
570+ "{\n \" id\" : \" abc\" \n}"
571+
540572 """
541- @ spec encode_to_iodata! ( term ( ) , encoder ( ) ) :: iodata ( )
542- def encode_to_iodata! ( term , encoder \\ & protocol_encode / 2 ) do
573+ @ spec encode_to_iodata! ( term ( ) , encoder ( ) | [ pretty: boolean ( ) | binary ( ) ] ) :: iodata ( )
574+ def encode_to_iodata! ( term , encoder_or_opts \\ & protocol_encode / 2 )
575+
576+ def encode_to_iodata! ( term , encoder ) when is_function ( encoder , 2 ) do
543577 encoder . ( term , encoder )
544578 end
545579
580+ def encode_to_iodata! ( term , opts ) when is_list ( opts ) do
581+ iodata = protocol_encode ( term , & protocol_encode / 2 )
582+
583+ case Keyword . fetch ( validate_opts! ( opts ) , :pretty ) do
584+ { :ok , false } ->
585+ iodata
586+
587+ { :ok , true } ->
588+ format_iodata ( iodata , " " )
589+
590+ { :ok , unit } when is_binary ( unit ) ->
591+ format_iodata ( iodata , indent_unit! ( unit ) )
592+
593+ { :ok , other } ->
594+ raise ArgumentError ,
595+ "expected :pretty to be a boolean or a binary, got: #{ inspect ( other ) } "
596+
597+ :error ->
598+ iodata
599+ end
600+ end
601+
602+ defp validate_opts! ( opts ) do
603+ if Keyword . keyword? ( opts ) do
604+ case Keyword . keys ( opts ) -- [ :pretty ] do
605+ [ ] ->
606+ opts
607+
608+ unknown ->
609+ raise ArgumentError ,
610+ "unknown options #{ inspect ( unknown ) } , expected only :pretty"
611+ end
612+ else
613+ raise ArgumentError ,
614+ "expected the second argument to be a function or a keyword list, " <>
615+ "got: #{ inspect ( opts ) } "
616+ end
617+ end
618+
619+ defp indent_unit! ( unit ) do
620+ if indent_unit? ( unit ) do
621+ unit
622+ else
623+ raise ArgumentError ,
624+ "expected :pretty to be a binary of spaces and tabs, got: #{ inspect ( unit ) } "
625+ end
626+ end
627+
628+ defp indent_unit? ( << char , rest :: binary >> ) when char in ~c" \t " , do: indent_unit? ( rest )
629+ defp indent_unit? ( << >> ) , do: true
630+ defp indent_unit? ( _ ) , do: false
631+
632+ defp format_iodata ( iodata , unit ) do
633+ # Flattening is required: the iodata mixes binaries with byte separators,
634+ # so string boundaries do not line up with iodata elements.
635+ indents = List . to_tuple ( for depth <- 0 .. @ max_indent , do: :binary . copy ( unit , depth ) )
636+ format_value ( IO . iodata_to_binary ( iodata ) , 0 , indents , [ ] )
637+ end
638+
639+ defp format_value ( << >> , _depth , _pad , acc ) , do: acc
640+
641+ defp format_value ( << ?" , rest :: binary >> , depth , indents , acc ) do
642+ case format_string ( rest , [ ] ) do
643+ { :ok , string , rest } ->
644+ format_value ( rest , depth , indents , [ acc , ?" , string , ?" ] )
645+
646+ :eof ->
647+ raise ArgumentError ,
648+ "expected the encoder to return valid JSON, got an unterminated string"
649+ end
650+ end
651+
652+ defp format_value ( << ?{ , rest :: binary >> , depth , indents , acc ) do
653+ case skip_whitespace ( rest ) do
654+ << ?} , rest :: binary >> -> format_value ( rest , depth , indents , [ acc , "{}" ] )
655+ rest -> format_value ( rest , depth + 1 , indents , [ acc , ?{ , newline ( depth + 1 , indents ) ] )
656+ end
657+ end
658+
659+ defp format_value ( << ?[ , rest :: binary >> , depth , indents , acc ) do
660+ case skip_whitespace ( rest ) do
661+ << ?] , rest :: binary >> -> format_value ( rest , depth , indents , [ acc , "[]" ] )
662+ rest -> format_value ( rest , depth + 1 , indents , [ acc , ?[ , newline ( depth + 1 , indents ) ] )
663+ end
664+ end
665+
666+ defp format_value ( << ?} , rest :: binary >> , depth , indents , acc ) when depth > 0 do
667+ format_value ( rest , depth - 1 , indents , [ acc , newline ( depth - 1 , indents ) , ?} ] )
668+ end
669+
670+ defp format_value ( << ?] , rest :: binary >> , depth , indents , acc ) when depth > 0 do
671+ format_value ( rest , depth - 1 , indents , [ acc , newline ( depth - 1 , indents ) , ?] ] )
672+ end
673+
674+ defp format_value ( << ?, , rest :: binary >> , depth , indents , acc ) do
675+ format_value ( rest , depth , indents , [ acc , ?, , newline ( depth , indents ) ] )
676+ end
677+
678+ defp format_value ( << ?: , rest :: binary >> , depth , indents , acc ) do
679+ format_value ( rest , depth , indents , [ acc , ": " ] )
680+ end
681+
682+ defp format_value ( << char , rest :: binary >> , depth , indents , acc ) when char in ~c" \t \r \n " do
683+ format_value ( rest , depth , indents , acc )
684+ end
685+
686+ defp format_value ( << char , _ :: binary >> = binary , depth , indents , acc )
687+ when char not in @ indent_specials do
688+ length = plain_length ( binary , 0 )
689+ rest = binary_part ( binary , length , byte_size ( binary ) - length )
690+ format_value ( rest , depth , indents , [ acc , binary_part ( binary , 0 , length ) ] )
691+ end
692+
693+ defp format_value ( << char , _ :: binary >> , 0 , _pad , _acc ) when char in [ ?} , ?] ] do
694+ raise ArgumentError ,
695+ "expected the encoder to return valid JSON, got unexpected #{ << char >> } "
696+ end
697+
698+ defp plain_length ( << char , rest :: binary >> , count ) when char not in @ indent_specials ,
699+ do: plain_length ( rest , count + 1 )
700+
701+ defp plain_length ( << _ :: binary >> , count ) , do: count
702+
703+ defp format_string ( << ?\\ , char , rest :: binary >> , acc ) ,
704+ do: format_string ( rest , [ acc , ?\\ , char ] )
705+
706+ defp format_string ( << ?" , rest :: binary >> , acc ) , do: { :ok , acc , rest }
707+ defp format_string ( << >> , _acc ) , do: :eof
708+
709+ defp format_string ( binary , acc ) do
710+ length = string_length ( binary , 0 )
711+ rest = binary_part ( binary , length , byte_size ( binary ) - length )
712+ format_string ( rest , [ acc , binary_part ( binary , 0 , length ) ] )
713+ end
714+
715+ defp string_length ( << char , rest :: binary >> , count ) when char not in [ ?" , ?\\ ] ,
716+ do: string_length ( rest , count + 1 )
717+
718+ defp string_length ( << _ :: binary >> , count ) , do: count
719+
720+ defp skip_whitespace ( << char , rest :: binary >> ) when char in ~c" \t \r \n " ,
721+ do: skip_whitespace ( rest )
722+
723+ defp skip_whitespace ( binary ) , do: binary
724+
725+ defp newline ( depth , indents ) when depth < tuple_size ( indents ) ,
726+ do: [ ?\n , elem ( indents , depth ) ]
727+
728+ defp newline ( depth , indents ) , do: [ ?\n , :binary . copy ( elem ( indents , 1 ) , depth ) ]
729+
546730 @ doc """
547731 This is the default encode implementation passed to `encode!/1`.
548732
0 commit comments