protobuf ではなく JSON でやり取りするファイルを出力する protoc プラグイン

Related tags

protoc-gen-jsonif
Overview

protoc-gen-jsonif

proto ファイルから、JSON フォーマットでやりとりする型定義ファイルを出力する protoc プラグインです。

  • proto ファイルで言語を越えて型定義が出来るのはとても良い
  • しかし protobuf ライブラリを入れるのが面倒
  • 今のプロジェクトには既に JSON ライブラリが入っているので JSON でやり取りしたい

という時に使うプラグインです。

実装状況

  • C++ 用コードの出力 (Boost.JSON 利用)
  • Unity 用コードの出力
  • message, enum 対応
  • repeated 対応
  • oneof 対応
  • オブジェクトの等値判定対応
  • テスト
  • 自動ビルド環境

対応するかもしれないもの

  • C++, Unity 以外の言語への対応
  • bytes 型の対応
  • オブジェクトの大小の比較

対応する予定が無いもの

  • proto2 シンタックス対応
  • map, any 型の対応
  • service 定義の対応
  • 実行速度の最適化(速度が欲しいならちゃんと protobuf 入れましょう)

使い方

まず、protobuf のリリース から、自身のプラットフォームの最新のバイナリをダウンロードして下さい。 Windows なら protoc-<version>-win64.zip、macOS なら protoc-<version>-osx-x86_64.zip などです。

ダウンロードが完了したら、これを展開し、protoc/bin ディレクトリに環境変数 PATH を通しておいて下さい。

次に、protoc-gen-jsonif のリリース から、protoc-gen-jsonif.tar.gz をダウンロードして下さい。

ダウンロードが完了したら、これを展開し、自身のプラットフォームのディレクトリに環境変数 PATH を通しておいて下さい。 Windows なら protoc-gen-jsonif/windows/amd64、macOS なら protoc-gen-jsonif/macos/amd64 などです。

次に以下の内容を test.proto として保存して下さい。

syntax = "proto3";

package test;

message Person {
  string name = 1;
}

あとは以下のように実行して test.proto ファイルを変換します。

mkdir -p out_cpp
protoc --jsonif-cpp_out=out_cpp/

これで out_cpp/ ディレクトリに C++ 用のファイルが出力されます。

また、

mkdir -p out_unity
protoc --jsonif-unity_out=out_unity/

こうすると out_unity/ ディレクトリに Unity 用のファイルが出力されます。

PATH を通さずに実行する

環境変数 PATH を設定しなくても、以下のように指定すれば実行できます。

Windows 上で、ダウンロードした protoc が ./protoc に、protoc-gen-jsonif が ./protoc-gen-jsonif にあるという状態だとすると、

# C++
mkdir -p out_cpp
./protoc/bin/protoc.exe \
  --plugin=protoc-gen-jsonif-cpp=./protoc-gen-jsonif/windows/amd64/protoc-gen-jsonif-cpp.exe \
  --jsonif-cpp_out=out_cpp/ \
  test.proto

# Unity
mkdir -p out_unity
./protoc/bin/protoc.exe \
  --plugin=protoc-gen-jsonif-unity=./protoc-gen-jsonif/windows/amd64/protoc-gen-jsonif-unity.exe \
  --jsonif-unity_out=out_unity/ \
  test.proto

これで PATH を設定しなくても変換できます。

例では全て、以下のような test.proto ファイルがあるとしています。

syntax = "proto3";

package test;

message Person {
  string name = 1;
}
message PersonList {
  repeated Person people = 1;
}

C++

C++ 用のファイルを出力するには以下のように利用します。

protoc --jsonif-cpp_out=out/ test.proto

こうすると C++ ファイルが自動生成されて、以下のように型定義を使って JSON 文字列をシリアライズ・デシリアライズ可能になります。

#include "test.json.h"

int main() {
  test::PersonList p;

  p.people.push_back(test::Person{"hoge"});
  p.people.push_back(test::Person{"fuga"});

  // JSON 文字列への変換
  std::string str = jsonif::to_json(p);

  std::cout << str << std::endl;
  // → {"people":[{"name":"hoge"},{"name":"fuga"}]}

  // JSON 文字列から元に戻す
  p = jsonif::from_json<test::PersonList>(str);

  std::cout << p[0].name << std::endl;
  // → hoge

  std::cout << p[1].name << std::endl;
  // → fuga
}

自動生成された test.json.h は以下のようになっています(若干変わっている可能性もあります)。 C++ 標準には JSON ライブラリが無いため、現在は Boost.JSON を利用しています。

#include <string>
#include <vector>
#include <stddef.h>

#include <boost/json.hpp>


namespace test {

struct Person {
  std::string name;
};

struct PersonList {
  std::vector<::test::Person> people;
};

// ::test::Person
void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, const ::test::Person& v) {
  jv = {
    {"name", boost::json::value_from(v.name)},
  };
}

::test::Person tag_invoke(const boost::json::value_to_tag<::test::Person>&, const boost::json::value& jv) {
  ::test::Person v;
  v.name = boost::json::value_to<std::string>(jv.at("name"));
  return v;
}

// ::test::PersonList
void tag_invoke(const boost::json::value_from_tag&, boost::json::value& jv, const ::test::PersonList& v) {
  jv = {
    {"people", boost::json::value_from(v.people)},
  };
}

::test::PersonList tag_invoke(const boost::json::value_to_tag<::test::PersonList>&, const boost::json::value& jv) {
  ::test::PersonList v;
  v.people = boost::json::value_to<std::vector<::test::Person>>(jv.at("people"));
  return v;
}


}

#ifndef JSONIF_HELPER_DEFINED
#define JSONIF_HELPER_DEFINED

namespace jsonif {

template<class T>
inline T from_json(const std::string& s) {
  return boost::json::value_to<T>(boost::json::parse(s));
}

template<class T>
inline std::string to_json(const T& v) {
  return boost::json::serialize(boost::json::value_from(v));
}

}

#endif

Unity

Unity 用のファイルを出力するには以下のように利用します。

protoc --jsonif-unity_out=out/ test.proto

こうすると Unity 用の C# ファイルが自動生成されて、以下のように JSON 文字列をシリアライズ・デシリアライズ可能になります。

    void Start()
    {
        var p = new Test.PersonList();

        p.people.Add(new Test.Person() { name = "hoge" });
        p.people.Add(new Test.Person() { name = "fuga" });

        // JSON 文字列への変換
        string str = Jsonif.Json.ToJson(p);

        Debug.Log(str);
        // → {"people":[{"name":"hoge"},{"name":"fuga"}]}

        // JSON 文字列から元に戻す
        p = Jsonif.Json.FromJson<Test.PersonList>(str);

        Debug.Log(p.people[0].name);
        // → hoge

        Debug.Log(p.people[1].name);
        // → fuga
    }

自動生成された Test.csJsonif.cs は以下のようになっています(若干変わっている可能性もあります)。 Unity では内部的に JsonUtility を利用しています。

// Test.cs
namespace Test
{
    
    [System.Serializable]
    public class Person
    {
        public string name;
    }
    
    [System.Serializable]
    public class PersonList
    {
        public global::Test.Person[] people;
    }
    
}
// Jsonif.cs
using UnityEngine;

namespace Jsonif
{
    
    public static class Json
    {
        public static string ToJson<T>(T v)
        {
            return JsonUtility.ToJson(v);
        }
        public static T FromJson<T>(string s)
        {
            return JsonUtility.FromJson<T>(s);
        }
    }
    
}

FAQ

Q. jsonif って何?

A. JSON Interface の略です。

本当は protoc-gen-json という名前にしようと思ってたのですが、protoc-gen-json というのは既に存在 していて、これは proto ファイルそのものを JSON 化するものです。 これは欲しい機能ではなかったし、単に protoc-gen-json というとこっちを指すんだとするとちょっと違うので、Interface を付けて別の名前にしました。

Q. protoc-gen って何?

A. protoc で生成するプラグインの命名ルールです。

protoc は、デフォルトでは --<NAME>_out=... と指定したら protoc-gen-<NAME> プログラムを実行してファイル生成を呼び出す仕組みになっています(--plugin オプションで上書きできます)。 そのため protoc プラグインのリポジトリ名やバイナリ名に protoc-gen- プリフィックスを付けるのが一般的です。

A new way of working with Protocol Buffers.

Buf All documentation is hosted at https://buf.build. Please head over there for more details. Goal Buf’s long-term goal is to enable schema-driven de

null 2.5k Jul 16, 2021
A Protocol Buffers compiler that generates optimized marshaling & unmarshaling Go code for ProtoBuf APIv2

vtprotobuf, the Vitess Protocol Buffers compiler This repository provides the protoc-gen-go-vtproto plug-in for protoc, which is used by Vitess to gen

PlanetScale 262 Jul 23, 2021
A simple RPC framework with protobuf service definitions

Twirp is a framework for service-to-service communication emphasizing simplicity and minimalism. It generates routing and serialization from API defin

Twitch 4.9k Jul 21, 2021
protoc-gen-grpc-gateway-ts is a Typescript client generator for the grpc-gateway project. It generates idiomatic Typescript clients that connect the web frontend and golang backend fronted by grpc-gateway.

protoc-gen-grpc-gateway-ts protoc-gen-grpc-gateway-ts is a Typescript client generator for the grpc-gateway project. It generates idiomatic Typescript

gRPC Ecosystem 25 Jul 21, 2021
Erda's infrastructure framework

Erda Infra Translations: English | 简体中文 Erda Infra is a lightweight microservices framework implements by golang, which offers many useful modules and

Erda 31 Jul 22, 2021
A protoc-gen-go wrapper including an RPC stub generator

// Copyright 2013 Google. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE fi

Kyle Lemons 35 May 13, 2020
Generate types and service clients from protobuf definitions annotated with http rules.

protoc-gen-typescript-http Generates Typescript types and service clients from protobuf definitions annotated with http rules. The generated types fol

Einride 14 Jul 7, 2021
Fast time-series data storage server accessible over gRPC

tstorage-server Persistent fast time-series data storage server accessible over gRPC. tstorage-server is lightweight local on-disk storage engine serv

Bartlomiej Mika 3 Jul 17, 2021
Create a gRPC server from code generated by sqlc

sqlc-grpc Create a gRPC Server from the generated code by the awesome sqlc project. Dependencies Go 1.16 or superior protoc sqlc, protoc-gen-go and pr

Walter Wanderley 35 Jul 8, 2021
Create a gRPC Server from Database

xo-grpc Create a gRPC Server from the generated code by the xo project. Requirements Go 1.16 or superior protoc xo, protoc-gen-go and protoc-gen-go-gr

Walter Wanderley 17 Jun 23, 2021
Easily generate gRPC services in Go ⚡️

Lile is a application generator (think create-react-app, rails new or django startproject) for gRPC services in Go and a set of tools/libraries. The p

Lile 1.3k Jul 18, 2021
Server for Pragyan's Dalal Street

Server for Dalal Street Prerequisites Go 1.13 Download link Protocol buffers Download link MySQL Check prerequisites Check the go version installed. g

Delta Force 56 Apr 8, 2021
A pluggable backend API that enforces the Event Sourcing Pattern for persisting & broadcasting application state changes

A pluggable "Application State Gateway" that enforces the Event Sourcing Pattern for securely persisting & broadcasting application state changes

null 24 Apr 15, 2021
🚀Gev is a lightweight, fast non-blocking TCP network library based on Reactor mode. Support custom protocols to quickly and easily build high-performance servers.

gev 中文 | English gev is a lightweight, fast non-blocking TCP network library based on Reactor mode. Support custom protocols to quickly and easily bui

徐旭 1.2k Jul 16, 2021