1

Here is the RESTful API response json:

{
    "success": true, 
    "data": {
        "loginname": "mrdulin", 
        "avatar_url": "https://avatars1.githubusercontent.com/u/1147375?v=4&s=120", 
        "githubUsername": "mrdulin", 
        "create_at": "2012-09-09T05:26:58.319Z", 
        "score": 15835, 
        "recent_topics": [
            {
                "id": "5c6d11d033b0b629ac8434ef", 
                "author": {
                    "loginname": "mrdulin", 
                    "avatar_url": "https://avatars1.githubusercontent.com/u/1147375?v=4&s=120"
                }, 
                "title": "grpc and Node.js", 
                "last_reply_at": "2019-05-11T04:22:18.616Z"
            }
        ],
        "recent_replies": []
    }
}

UserServiceImpl.ts:

export class UserServiceImpl implements IUserServiceServer {
  public async getUserByLoginname(call: ServerUnaryCall<GetUserByLoginnameRequest>, callback: sendUnaryData<GetUserByLoginnameResponse>) {
    const loginname = call.request.getLoginname();
    const url = `${config.CNODE_API_URL}/user/${loginname}`;
    try {
      const res = await axios.get(url);
      const data = res.data.data;
      // map API response js plain object to GetUserByLoginnameResponse
      const grcpResponse = new GetUserByLoginnameResponse();
      grcpResponse.setSuccess(res.data.success);
      const user = new UserDetail();
      user.setAvatarUrl(data.avatar_url);
      user.setLoginname(data.loginname);
      user.setGithubusername(data.githubUsername);
      user.setCreateAt(data.create_at);
      user.setScore(data.score);
      const recentReplies = data.recent_replies.map((po) => {
        const reply = new RecentReply();
        reply.setId(po.id);
        reply.setTitle(po.title);
        const lastReplyAt = new google_protobuf_timestamp_pb.Timestamp();
        lastReplyAt.fromDate(new Date(po.last_reply_at));
        reply.setLastReplyAt(lastReplyAt);
        const author = new UserBase();
        author.setLoginname(po.author.loginname);
        author.setAvatarUrl(po.author.avatar_url);
        reply.setAuthor(author);
        return reply;
      });
      const recentTopics = data.recent_topics.map((po) => {
        const topic = new TopicBase();
        topic.setId(po.id);
        topic.setTitle(po.title);
        topic.setLastReplyAt(po.last_reply_at);
        const author = new UserBase();
        author.setLoginname(po.author.loginname);
        author.setAvatarUrl(po.author.avatar_url);
        topic.setAuthor(author);
        return topic;
      });
      user.setRecentRepliesList(recentReplies);
      user.setRecentTopicsList(recentTopics);
      grcpResponse.setData(user);
      callback(null, grcpResponse);
    } catch (error) {
      console.error(error);
      const metadata = new Metadata();
      metadata.set('url', url);
      const ErrGetUserByLoginname: ServiceError = {
        code: status.INTERNAL,
        name: 'getUserByLoginnameError',
        message: 'call CNode API failed',
        metadata,
      };
      callback(ErrGetUserByLoginname, null);
    }
  }
}

As you can see, I have to map the plain javascript object(the API response) to GetUserByLoginnameResponse class manually. This is cumbersome and inefficient.

I am using static codegen, so the GetUserByLoginnameResponse class is generated based on IDL defined in the .proto files.

user.service.proto:

service UserService {
  rpc GetUserByLoginname(GetUserByLoginnameRequest)
      returns (GetUserByLoginnameResponse);
}
message GetUserByLoginnameRequest { string loginname = 1; }
message GetUserByLoginnameResponse {
  UserDetail data = 1;
  bool success = 2;
}
message UserDetail {
  string loginname = 1;
  string avatar_url = 2;
  string githubUsername = 3;
  string create_at = 4;
  int32 score = 5;
  repeated share.TopicBase recent_topics = 6;
  repeated reply.RecentReply recent_replies = 7;
}
Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Are these classes generated at runtime or are you able to modify them? If the latter applies you could maybe try this library: https://github.com/typestack/class-transformer – eol Aug 31 '20 at 08:00
  • @eol I get your point, I also thought about using this package before. But the `xxx_pb.js` files are generated and will be override for each compiling. Besides, there are `GENERATED CODE -- DO NOT EDIT!` comment at the top for these generated files. These classes generated at compile time, I am using static codegen, not dynamic codegen. – Lin Du Aug 31 '20 at 08:11
  • I see - afaik there's no other way to do it as you did unfortunately :/ But let's see, maybe someone else knows. – eol Aug 31 '20 at 08:14
  • See [JSON Mapping](https://developers.google.com/protocol-buffers/docs/proto3#json). I've used this with the Golang SDK but not Node.JS. If it's part of the Node SDK (!), you should be able to marshal protobuf to JSON and vice versa. – DazWilkin Aug 31 '20 at 20:29

0 Answers0