Skip to content

Instantly share code, notes, and snippets.

@tony612
Created August 10, 2018 06:23
Show Gist options
  • Save tony612/31f5a5c38403e67536cf57d8b9154535 to your computer and use it in GitHub Desktop.
Save tony612/31f5a5c38403e67536cf57d8b9154535 to your computer and use it in GitHub Desktop.
ruby hash to pb
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