Convert a Value proto to a JSON-serializable Python type. bytes_val and bytes_list_val are base64-encoded (RFC 4648) so that JSONResponse can serialize them without TypeError. This matches standard protobuf JSON encoding for bytes fields and is safe for all HTTP clients.
(v: Value)
| 68 | |
| 69 | |
| 70 | def _value_to_native(v: Value) -> Optional[Any]: |
| 71 | """Convert a Value proto to a JSON-serializable Python type. |
| 72 | |
| 73 | bytes_val and bytes_list_val are base64-encoded (RFC 4648) so that |
| 74 | JSONResponse can serialize them without TypeError. This matches standard |
| 75 | protobuf JSON encoding for bytes fields and is safe for all HTTP clients. |
| 76 | """ |
| 77 | which = v.WhichOneof("val") |
| 78 | if which is None or which == "null_val": |
| 79 | return None |
| 80 | # bytes must be base64-encoded for JSON serialization |
| 81 | elif which == "bytes_val": |
| 82 | return base64.b64encode(v.bytes_val).decode("ascii") |
| 83 | # RepeatedValue — nested Values that must be recursively converted |
| 84 | elif which in ("list_val", "set_val"): |
| 85 | return [_value_to_native(nested) for nested in getattr(v, which).val] |
| 86 | # Map<string, Value> — recursively convert nested Values |
| 87 | elif which in ("map_val", "struct_val"): |
| 88 | return {k: _value_to_native(vv) for k, vv in getattr(v, which).val.items()} |
| 89 | # MapList — list of Map<string, Value> |
| 90 | elif which in ("map_list_val", "struct_list_val"): |
| 91 | return [ |
| 92 | {k: _value_to_native(vv) for k, vv in m.val.items()} |
| 93 | for m in getattr(v, which).val |
| 94 | ] |
| 95 | # scalar_map_val has non-string keys; full conversion requires extra work and |
| 96 | # this type is not returned by standard get_online_features paths today. |
| 97 | elif which == "scalar_map_val": |
| 98 | logger.warning( |
| 99 | "scalar_map_val is not yet supported by convert_response_to_dict; value will be None" |
| 100 | ) |
| 101 | return None |
| 102 | # bytes_list_val / bytes_set_val — base64-encode each element |
| 103 | elif which in ("bytes_list_val", "bytes_set_val"): |
| 104 | return [base64.b64encode(b).decode("ascii") for b in getattr(v, which).val] |
| 105 | # All other list/set types have scalar .val fields |
| 106 | elif "_list_" in which or "_set_" in which: |
| 107 | return list(getattr(v, which).val) |
| 108 | else: |
| 109 | return getattr(v, which) |
| 110 | |
| 111 | |
| 112 | def _timestamp_to_str(ts) -> str: |