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

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay