Protobuf.JSON (protobuf v0.8.0) View Source

JSON encoding and decoding utilities for Protobuf structs.

It follows Google's specs and reference implementation. Only proto3 syntax is supported at the moment. Some features such as well-known types are not fully supported yet.

Types

Protobuf messages and embedded messages are encoded as objects, repeated fields as arrays, bytes as Base64 strings, bool as booleans, map as objects and so on.

proto3JSONSupported
messageobjectYes
enumstringYes
map<K,V>objectYes
repeated Varray [v, …]Yes
booltrue, falseYes
stringstringYes
bytesbase64 stringYes
int32, fixed32, uint32numberYes
int64, fixed64, uint64stringYes
float, doublenumberYes
AnyobjectNo
TimestampstringNo
DurationstringNo
StructobjectNo
Wrapper typesvarious typesNo
FieldMaskstringNo
ListValuearray [foo, bar, …]No
ValuevalueNo
NullValuenullNo
EmptyobjectNo

Usage

Protobuf.JSON requires a JSON library to work, so first make sure you have :jason added to your dependencies:

defp deps do
  [
    {:jason, "~> 1.2"},
    # ...
  ]
end

With encode/1 you can turn any Protobuf message struct into a JSON string:

iex> message = Car.new(color: :RED, top_speed: 125.3)
iex> Protobuf.JSON.encode(message)
{:ok, "{\"color\":\"RED\",\"topSpeed\":125.3}"}

And go the other way around with decode/1:

iex> json = ~S|{"color":"RED","topSpeed":125.3}|
iex> Protobuf.JSON.decode(json, Car)
{:ok, %Car{color: :RED, top_speed: 125.3}}

JSON keys are encoded as camelCase strings by default, specified by the json_name field option. So make sure to recompile the .proto files in your project before working with JSON encoding, the compiler will generate all the required json_name options. You can set your own json_name for a particular field too:

message GeoCoordinate {
  double latitude = 1 [ json_name = "lat" ];
  double longitude = 2 [ json_name = "long" ];
}

Known issues and limitations

Currently, the protoc compiler won't check for field name collisions, this library either. So make sure your field names will be unique when serialized to JSON. For instance, this message definition will not encode correctly, it will emit just one of the two fields, and the problem might go unnoticed:

message CollidingFields {
  int32 f1 = 1 [json_name = "sameName"];
  float f2 = 2 [json_name = "sameName"];
}

According to the specification, when duplicated JSON keys are found in maps, the library should raise a decoding error. It currently ignores duplicates and keeps the last occurrence.

Link to this section Summary

Functions

Decodes a JSON iodata into a module Protobuf struct.

Decodes a JSON iodata into a module Protobuf struct.

Generates a JSON representation of the given protobuf struct.

Generates a JSON representation of the given protobuf struct.

Decodes a json_data map into a module Protobuf struct.

Generates a JSON-encodable map for the given protobuf struct.

Link to this section Types

Specs

encode_opt() ::
  {:use_proto_names, boolean()}
  | {:use_enum_numbers, boolean()}
  | {:emit_unpopulated, boolean()}

Specs

json_data() :: %{optional(binary()) => any()}

Link to this section Functions

Specs

decode(iodata(), module()) ::
  {:ok, struct()} | {:error, Protobuf.JSON.DecodeError.t() | Exception.t()}

Decodes a JSON iodata into a module Protobuf struct.

Examples

Given these proto modules:

syntax = "proto3";

message Car {
  enum Color {
    GREEN = 0;
    RED = 1;
  }

  Color color = 1;
  float top_speed = 2;
}

You can build their structs from JSON like so:

iex> Protobuf.JSON.decode("{}", Car)
{:ok, %Car{color: :GREEN, top_speed: 0.0}}

iex> ~S|{"color":"RED"}| |> Protobuf.JSON.decode(Car)
{:ok, %Car{color: :RED, top_speed: 0.0}}

iex> ~S|{"color":"GREEN","topSpeed":80.0}| |> Protobuf.JSON.decode(Car)
{:ok, %Car{color: :GREEN, top_speed: 80.0}}

Specs

decode!(iodata(), module()) :: struct() | no_return()

Decodes a JSON iodata into a module Protobuf struct.

Similar to decode!/2 except it will unwrap the error tuple and raise in case of errors.

Examples

iex> Protobuf.JSON.decode!("{}", Car)
%Car{color: :GREEN, top_speed: 0.0}

iex> ~S|{"color":"RED"}| |> Protobuf.JSON.decode!(Car)
%Car{color: :RED, top_speed: 0.0}

iex> ~S|{"color":"GREEN","topSpeed":80.0}| |> Protobuf.JSON.decode!(Car)
%Car{color: :GREEN, top_speed: 80.0}
Link to this function

encode(struct, opts \\ [])

View Source

Specs

encode(struct(), [encode_opt()]) ::
  {:ok, String.t()} | {:error, Protobuf.JSON.EncodeError.t() | Exception.t()}

Generates a JSON representation of the given protobuf struct.

Options

  • :use_proto_names - use original field name instead of the camelCase json_name for JSON keys (default is false).
  • :use_enum_numbers - encode enum field values as numbers instead of their labels (default is false).
  • :emit_unpopulated - emit all fields, even when they are blank, empty or set to their default value (default is false).

Examples

Suppose these are you proto modules:

syntax = "proto3";

message Car {
  enum Color {
    GREEN = 0;
    RED = 1;
  }

  Color color = 1;
  float top_speed = 2;
}

Encoding should be as simple as:

iex> Car.new(color: :RED, top_speed: 125.3) |> Protobuf.JSON.encode()
{:ok, ~S|{"color":"RED","topSpeed":125.3}|}

iex> Car.new(color: :GREEN) |> Protobuf.JSON.encode()
{:ok, "{}"}

iex> Car.new() |> Protobuf.JSON.encode(emit_unpopulated: true)
{:ok, ~S|{"color":"GREEN","topSpeed":0.0}|}
Link to this function

encode!(struct, opts \\ [])

View Source

Specs

encode!(struct(), [encode_opt()]) :: String.t() | no_return()

Generates a JSON representation of the given protobuf struct.

Similar to encode/2 except it will unwrap the error tuple and raise in case of errors.

Examples

iex> Car.new(top_speed: 80.0) |> Protobuf.JSON.encode!()
~S|{"topSpeed":80.0}|

iex> TestMsg.Foo2.new() |> Protobuf.JSON.encode!()
** (Protobuf.JSON.EncodeError) JSON encoding of 'proto2' syntax is unsupported, try proto3
Link to this function

from_decoded(json_data, module)

View Source

Specs

from_decoded(json_data(), module()) ::
  {:ok, struct()} | {:error, Protobuf.JSON.DecodeError.t()}

Decodes a json_data map into a module Protobuf struct.

Similar to decode/2 except it takes a JSON map representation of the data.

Examples

iex> Protobuf.JSON.from_decoded(%{}, Car)
{:ok, %Car{color: :GREEN, top_speed: 0.0}}

iex> Protobuf.JSON.from_decoded(%{"color" => "RED"}, Car)
{:ok, %Car{color: :RED, top_speed: 0.0}}

iex> Protobuf.JSON.from_decoded(%{"color" => "GREEN","topSpeed" => 80.0}, Car)
{:ok, %Car{color: :GREEN, top_speed: 80.0}}
Link to this function

to_encodable(struct, opts \\ [])

View Source

Specs

to_encodable(struct(), [encode_opt()]) ::
  {:ok, json_data()} | {:error, Protobuf.JSON.EncodeError.t()}

Generates a JSON-encodable map for the given protobuf struct.

Similar to encode/2 except it will return an intermediate map representation.

Examples

iex> Car.new(color: :RED, top_speed: 125.3) |> Protobuf.JSON.to_encodable()
{:ok, %{"color" => :RED, "topSpeed" => 125.3}}

iex> Car.new(color: :GREEN) |> Protobuf.JSON.to_encodable()
{:ok, %{}}

iex> Car.new() |> Protobuf.JSON.to_encodable(emit_unpopulated: true)
{:ok, %{"color" => :GREEN, "topSpeed" => 0.0}}