DEV Community

Stanislav Berkov
Stanislav Berkov

Posted on

Protobuf fake data generator

Let's assume you have protobuf proto files, generated C# message classes for them, and you want to generate fake data for demo purposes. You can use Google.Protobuf.Reflection for it as follows:

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());
    }
}
*/
Enter fullscreen mode Exit fullscreen mode

https://gist.github.com/stasberkov/2d12925ea1d30c712fe9240bd5c7ea8b

Top comments (0)