Created
January 30, 2025 16:07
-
-
Save stasberkov/2d12925ea1d30c712fe9240bd5c7ea8b to your computer and use it in GitHub Desktop.
Protobuf fake data generator
This file contains 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
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Google.Protobuf; | |
using Google.Protobuf.Reflection; | |
using Google.Protobuf.Collections; | |
using Google.Protobuf.WellKnownTypes; | |
using System.Collections; | |
public class ProtobufFaker { | |
private readonly Random _random; | |
private readonly int _maxCollectionSize; | |
private readonly int _stringMaxLength; | |
public ProtobufFaker(int? seed = null, int maxCollectionSize = 5, int stringMaxLength = 20) { | |
_random = seed.HasValue ? new Random(seed.Value) : new Random(); | |
_maxCollectionSize = maxCollectionSize; | |
_stringMaxLength = stringMaxLength; | |
} | |
public T Generate<T>() where T : IMessage, new() { | |
var message = new T(); | |
PopulateMessage(message); | |
return message; | |
} | |
public void PopulateMessage(IMessage message) { | |
var descriptor = message.Descriptor; | |
foreach (var fieldDescriptor in descriptor.Fields.InDeclarationOrder()) { | |
if (fieldDescriptor.IsRepeated) { | |
var list = (IList)fieldDescriptor.Accessor.GetValue(message); | |
var count = _random.Next(1, _maxCollectionSize + 1); | |
for (int i = 0; i < count; i++) { | |
var value = GenerateSingleValue(fieldDescriptor); | |
if (value != null) { | |
list.Add(value); | |
} | |
} | |
} | |
else { | |
var value = GenerateFieldValue(fieldDescriptor); | |
if (value != null) { | |
fieldDescriptor.Accessor.SetValue(message, value); | |
} | |
} | |
} | |
} | |
private object? GenerateFieldValue(FieldDescriptor field) { | |
if (field.IsMap) { | |
return GenerateMapField(field); | |
} | |
// Handle oneof fields | |
if (field.ContainingOneof != null) { | |
// Randomly decide whether to set this oneof field | |
if (_random.Next(2) == 0) { | |
return null; | |
} | |
} | |
return GenerateSingleValue(field); | |
} | |
private object? GenerateSingleValue(FieldDescriptor field) { | |
if (field.FieldType == FieldType.Message && field.MessageType.FullName == "google.protobuf.Timestamp") { | |
return GenerateTimestamp(); | |
} | |
return field.FieldType switch { | |
FieldType.Double => _random.NextDouble() * 1000, | |
FieldType.Float => (float)(_random.NextDouble() * 1000), | |
FieldType.Int64 or FieldType.SInt64 or FieldType.SFixed64 => (long)_random.Next(1, 1000), | |
FieldType.UInt64 or FieldType.Fixed64 => (ulong)_random.Next(1, 1000), | |
FieldType.Int32 or FieldType.SInt32 or FieldType.SFixed32 => _random.Next(1, 1000), | |
FieldType.Fixed32 or FieldType.UInt32 => (uint)_random.Next(1, 1000), | |
FieldType.Bool => _random.Next(2) == 1, | |
FieldType.String => GenerateRandomString(), | |
FieldType.Bytes => GenerateRandomBytes(), | |
FieldType.Enum => GenerateEnumValue(field.EnumType), | |
FieldType.Message => GenerateNestedMessage(field.MessageType), | |
_ => null, | |
}; | |
} | |
private object? GenerateMapField(FieldDescriptor field) { | |
var count = _random.Next(1, _maxCollectionSize + 1); | |
var mapField = field.Accessor.GetValue((IMessage?)Activator.CreateInstance(field.ContainingType.ClrType)); | |
var mapDescriptor = field.MessageType; | |
var keyDescriptor = mapDescriptor.FindFieldByNumber(1); // Key is always field 1 | |
var valueDescriptor = mapDescriptor.FindFieldByNumber(2); // Value is always field 2 | |
var addMethod = mapField.GetType().GetMethod("Add"); | |
if (addMethod == null) { return null; } | |
for (int i = 0; i < count; i++) { | |
var key = GenerateSingleValue(keyDescriptor); | |
var value = GenerateSingleValue(valueDescriptor); | |
if (key != null && value != null) { | |
addMethod.Invoke(mapField, [key, value]); | |
} | |
} | |
return mapField; | |
} | |
private IMessage? GenerateNestedMessage(MessageDescriptor messageDescriptor) { | |
var message = (IMessage?)Activator.CreateInstance(messageDescriptor.ClrType); | |
if (message == null) { return null; } | |
PopulateMessage(message); | |
return message; | |
} | |
private object GenerateEnumValue(EnumDescriptor enumDescriptor) { | |
var values = enumDescriptor.Values; | |
var randomIndex = _random.Next(values.Count); | |
return values[randomIndex].Number; | |
} | |
private Timestamp GenerateTimestamp() { | |
var now = DateTime.UtcNow; | |
var daysOffset = _random.Next(-365 * 2, 365 * 2); // ±2 years from now | |
var hoursOffset = _random.Next(-24, 24); | |
var minutesOffset = _random.Next(-60, 60); | |
var randomTime = now | |
.AddDays(daysOffset) | |
.AddHours(hoursOffset) | |
.AddMinutes(minutesOffset); | |
return Timestamp.FromDateTime(randomTime); | |
} | |
private string GenerateRandomString() { | |
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |
var length = _random.Next(1, _stringMaxLength + 1); | |
return new string(Enumerable.Repeat(chars, length) | |
.Select(s => s[_random.Next(s.Length)]).ToArray()); | |
} | |
private ByteString GenerateRandomBytes() { | |
var length = _random.Next(1, _stringMaxLength + 1); | |
var bytes = new byte[length]; | |
_random.NextBytes(bytes); | |
return ByteString.CopyFrom(bytes); | |
} | |
} | |
// Example usage: | |
/* | |
public class Program | |
{ | |
public static void Main() | |
{ | |
var faker = new ProtobufFaker(); | |
// Generate fake data for any protobuf message | |
var message = faker.Generate<YourProtobufMessage>(); | |
// The faker will properly handle: | |
// - All protobuf field types | |
// - Repeated fields | |
// - Map fields | |
// - Oneof fields | |
// - Nested messages | |
// - Enums | |
// - Optional fields | |
Console.WriteLine(message.ToString()); | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment