Method 1
Problem 1: struct1->hex1, struct1->hext2, struct->num1, and struct1->num2 access the memory of data, which has effective type uint8_t (which is likely a character type), but they access that memory with a structure type. The behavior of this is undefined because it does not conform to the aliasing rules in C 20186 6.5 7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types: …
(There is a complication here in that the aliasing rules allow “an aggregate or union type that includes one of the aforementioned types among its members,” but I would not expect the nominal use of uint8_t in declaring the bit-field members satisfy that.)
This problem might be fixed by changing RandomStruct *struct1 = (RandomStruct *)data; to RandomStruct struct1;, using memcpy(&struct1, data, sizeof struct1); and changing the subsequent struct1-> occurrences to struct1..
Problem 2: The C standard does not specify the order in which hex2 and hex1 are located in the storage unit used to hold them, per C 2018 6.7.2.1 11:
… The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined…
Note that the order of allocation of bit-fields is not determined by the endianness of the C implementation. Regardless of whether a C implementation puts low-significance bytes of integer types earlier in memory or later in memory, it may place the first bit-field of a structure in the low bits of the storage unit or in the high bits of the storage unit.
This cannot be fixed portably in C (that is, using only strictly conforming code); you must ensure the members are declared in the necessary order for each C implementation you use.
Problem 3: The two bit-fields of the structure do not necessarily occupy just one byte. C 2018 6.7.2.1 11 says:
An implementation may allocate any addressable storage unit large enough to hold a bit-field…
This is a separate issue from packing the structure, as the storage unit used for bit-fields would be considered as bytes used for the bit-fields, not padding bytes that are eliminated by packing.
This cannot be fixed portably in C but a violation of the desired layout could be detected with _Static_assert(sizeof (RandomStruct) == 5, "Error, expected just five bytes in RandomStruct.");.
Method 2
Problem 1: data[1] | (data[2] << 8) is not guaranteed to work in every C implementation because data[2] will be promoted to int, and the C standard allows int to be 16 bits. If the high bit of data[2] is set, then data[2] << 8 would produce a result greater than or equal to 32,768, which is not representable in a 16-bit int. Then overflow occurs, and the behavior is not defined by the C standard, per C 2018 6.5.7 4:
… If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
This could be fixed by casting data[2] to uint16_t and similarly for data[4].