Created
August 10, 2018 06:23
-
-
Save tony612/31f5a5c38403e67536cf57d8b9154535 to your computer and use it in GitHub Desktop.
ruby hash to pb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'google/protobuf/timestamp_pb' | |
# Use official Message.new() instead | |
class Pb | |
class << self | |
def message_to_pb(message, msg_klass) | |
unless msg_klass.ancestors.include?(Google::Protobuf::MessageExts) | |
raise ArgumentError, "message class should be a pb message" | |
end | |
if msg_klass == Google::Protobuf::Timestamp && message.is_a?(::Time) | |
return pb_timestamp(message) | |
end | |
pb = msg_klass.new | |
hash = stringify_hash(message) | |
desc = msg_klass.descriptor | |
fields = desc.inject({}) do |acc, field| | |
acc[field.name] = field | |
acc | |
end | |
hash.each do |k, v| | |
field = fields[k] | |
val = field_to_pb(desc, field, v) | |
# There seems a bug when assign a map to a field | |
if val.is_a?(Google::Protobuf::Map) | |
val.each do |map_k, map_v| | |
pb[k][map_k] = map_v | |
end | |
elsif field.label == :repeated && val.is_a?(Array) | |
val.each { |i| pb[k].push(i) } | |
else | |
pb[k] = val | |
end | |
end | |
pb | |
end | |
private | |
def field_to_pb(parent, field, val) | |
if field.label == :repeated && val.is_a?(Array) | |
val.map { |v| single_field_to_pb(parent, field, v) } | |
else | |
single_field_to_pb(parent, field, val) | |
end | |
end | |
def single_field_to_pb(parent, field, val) | |
case field.type | |
when :bytes, :string | |
val.to_s | |
when :message | |
subtype = field.subtype | |
# seems no direct way to tell if it's a map | |
if field.label == :repeated && subtype.count == 2 && val.is_a?(Hash) && | |
[parent.name, 'MapEntry', field.name].join('_') == subtype.name | |
map_pb = map_to_pb(val, subtype) | |
map_pb ? map_pb : message_to_pb(val, subtype.msgclass) | |
else | |
message_to_pb(val, subtype.msgclass) | |
end | |
when :enum, :int32, :uint64, :int64, :bool | |
val | |
else | |
val | |
end | |
end | |
def map_to_pb(hash, desc) | |
f_key, f_val = desc.entries | |
if !(f_key.name == "key" && f_val.name == "value") | |
# k, v should be in order, but still check them in case other situations | |
f_val, f_key = desc.entries | |
if !(f_key.name == "key" && f_val.name == "value") | |
# not map | |
return | |
end | |
end | |
map = if f_val.type == :message | |
Google::Protobuf::Map.new(f_key.type, :message, f_val.subtype.msgclass) | |
else | |
Google::Protobuf::Map.new(f_key.type, f_val.type) | |
end | |
hash.each do |k, v| | |
val = field_to_pb(desc, f_val, v) | |
map[force_simple_type(k, f_key.type)] = field_to_pb(desc, f_val, v) | |
end | |
map | |
end | |
def force_simple_type(v, type) | |
case type | |
when :string, :bytes | |
v.to_s | |
else | |
v | |
end | |
end | |
def field_real_type(field) | |
if field.subtype | |
field.subtype.msgclass | |
else | |
field.type | |
end | |
end | |
def stringify_hash(hash) | |
new_hash = {} | |
hash.each do |k, v| | |
new_hash[k.to_s] = v | |
end | |
new_hash | |
end | |
def pb_timestamp(time) | |
num = time.to_f | |
frac = (num.modulo(1) * 1000_1000_1000).round | |
Google::Protobuf::Timestamp.new(seconds: num.to_i, nanos: frac) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment