With C #, how can I convert an array of binary data bytes into a custom object that models the data?


Scenario: I have received raw binary data via HTTP and stored the data into a byte array. I have documentation that describes various fields that the binary data can represent, but the actual meaning of the data must be determined at run-time. For example, if the byte that represents the occurrence of an error = 1, then the meaning of the next byte changes.

Using C# with .NET 4, I want to create one or more classes that mirror the fields described in the documentation and then somehow initialize the classes using the byte array of binary data. I would like the solution to minimize code duplication and also to be modular and elegant.

I have looked into creating Serializable classes, but I don't see how that can work since I am starting with a byte array that was not created (and therefore, not serialized) by me.

I have also attempted to use generics and reflection in order to retrieve the sizes and types of the fields contained in custom classes. I then used that information to dynamically slice out data from the byte array and assign it to the corresponding fields. However, this method resulted in a lot of ugly, unmanageable code.

Any advice or pointers on designing an extensible, decoupled solution for this problem would be much appreciated.

Edit: Example of classes containing fields that mirror the fields in the specification

public class PriceHistoryResponse : BinaryResponse
    public List<Quote> quotes { get; set; }
    private CountData countData { get; set; }
    private EndingDelimiterSection endingDelimiterSection { get; set; }

    /* This code performs the logic needed to check for optional fields
    and to find the number of times that certain fields are repeated */
    public PriceHistoryResponse(byte[] responseBytes) : base(responseBytes)
        countData = new CountData();

        quotes = new List<Quote>();
        for (int i = 0; i < countData.quoteCount; i++)
            quotes.Add(new Quote());

            quotes[i].symbolData = new SymbolData();

            if (quotes[i].symbolData.errorCode == 1)
                quotes[i].errorData = new ErrorData();

            quotes[i].chartBarData = new ChartBarData();

            quotes[i].chartBars = new List<ChartBar>();
            for (int j = 0; j < quotes[i].chartBarData.chartBarCount; j++)
                quotes[i].chartBars.Add(new ChartBar());

        endingDelimiterSection = new EndingDelimiterSection();

class CountData : IResponseSection
    public int quoteCount { get; set; }

public class Quote
    public SymbolData symbolData { get; set; }
    public ErrorData errorData { get; set; }
    public ChartBarData chartBarData { get; set; }
    public List<ChartBar> chartBars { get; set; }

public class SymbolData : IResponseSection
   public string symbol { get; set; }
   public byte errorCode { get; set; }

public class ErrorData : IResponseSection
    public string errorText { get; set; }

public class ChartBarData : IResponseSection
    public int chartBarCount { get; set; }

public class ChartBar : IResponseSection
    public float close { get; set; }
    public float high { get; set; }
    public float low { get; set; }
    public float open { get; set; }
    public float volume { get; set; }
    public long timestamp { get; set; }

I pasted your code into VS, clicked 'Generate Method Stub' a few times and moved it around some. I guess this would do the trick.

The code you provide is pretty clever, it's a bit like a visitor pattern where overloading switches to the right method.

 public class BinaryResponse {

        private BinaryReader _rdr;
        public BinaryResponse(byte[] responseBytes) {
            _rdr = new BinaryReader(new MemoryStream(responseBytes)); // wrap the byte[] in a BinaryReader to be able to pop the bytes off the top

        protected void ParseResponseSection(CountData countData) {
            countData.quoteCount = _rdr.ReadInt16(); // guessing 64.000 quotes should be enough in one response, the documentation will have the type

        protected void ParseResponseSection(SymbolData symbolData) {
            symbolData.errorCode = _rdr.ReadByte(); // depending on your format, where is the ErrorCOde in the byte[]? the symbol might be first

            int symbolLength = _rdr.ReadInt16(); // if it's not written by a .Net WriteString on the other end better to read this count yourelf
            symbolData.symbol = new string(_rdr.ReadChars(symbolLength)); // read the chars and put into string

        protected void ParseResponseSection(ErrorData errorData) {
            int errorLenth = _rdr.ReadInt16();
            errorData.errorText = new string(_rdr.ReadChars(errorLenth));

        protected void ParseResponseSection(ChartBarData chartBarData) {
            chartBarData.chartBarCount = _rdr.ReadInt16();

        protected void ParseResponseSection(ChartBar chartBar) {
            // check the order with the documentation, also maybe some casting is needed because other types are in the byte[]
            chartBar.close = _rdr.ReadSingle();
            chartBar.high = _rdr.ReadSingle();
            chartBar.low = _rdr.ReadSingle();
            chartBar.open = _rdr.ReadSingle();
            chartBar.timestamp = _rdr.ReadInt64();

        protected void ParseResponseSection(EndingDelimiterSection endingDelimiterSection) {
            int checkValue = _rdr.ReadInt16();
            if (checkValue != 12345) throw new InvalidDataException("Corrupt Response! Expecting End Delimiter"); // assert that the end delimiter is some value

Is this what you're looking for? You didn't say anything about encoding, you may need to take that into account when reading bytes etc.

Regards Gert-Jan