public static Frame fromProto(Common.Frame proto) {
      List<Object> parsedRows = new ArrayList<>(proto.getRowsCount());
      for (Common.Row protoRow : proto.getRowsList()) {
        ArrayList<Object> row = new ArrayList<>(protoRow.getValueCount());
        for (Common.TypedValue protoElement : protoRow.getValueList()) {
          Object element;

          // TODO Should these be primitives or Objects?
          switch (protoElement.getType()) {
            case BYTE:
              element = Long.valueOf(protoElement.getNumberValue()).byteValue();
              break;
            case SHORT:
              element = Long.valueOf(protoElement.getNumberValue()).shortValue();
              break;
            case INTEGER:
              element = Long.valueOf(protoElement.getNumberValue()).intValue();
              break;
            case LONG:
              element = protoElement.getNumberValue();
              break;
            case FLOAT:
              element = Long.valueOf(protoElement.getNumberValue()).floatValue();
              break;
            case DOUBLE:
              element = Double.valueOf(protoElement.getDoubleValue());
              break;
            case NUMBER:
              // TODO more cases here to expand on? BigInteger?
              element = BigDecimal.valueOf(protoElement.getDoubleValue());
              break;
            case STRING:
              element = protoElement.getStringValue();
              break;
            case CHARACTER:
              // A single character in the string
              element = protoElement.getStringValue().charAt(0);
              break;
            case BYTE_STRING:
              element = protoElement.getBytesValues().toByteArray();
              break;
            case BOOLEAN:
              element = protoElement.getBoolValue();
              break;
            case NULL:
              element = null;
              break;
            default:
              throw new RuntimeException("Unhandled type: " + protoElement.getType());
          }

          row.add(element);
        }

        parsedRows.add(row);
      }

      return new Frame(proto.getOffset(), proto.getDone(), parsedRows);
    }
    public Common.Frame toProto() {
      Common.Frame.Builder builder = Common.Frame.newBuilder();

      builder.setDone(done).setOffset(offset);

      for (Object row : this.rows) {
        if (null == row) {
          // Does this need to be persisted for some reason?
          continue;
        }

        if (row instanceof Object[]) {
          final Common.Row.Builder rowBuilder = Common.Row.newBuilder();

          for (Object element : (Object[]) row) {
            final Common.TypedValue.Builder valueBuilder = Common.TypedValue.newBuilder();

            // Numbers
            if (element instanceof Byte) {
              valueBuilder.setType(Common.Rep.BYTE).setNumberValue(((Byte) element).longValue());
            } else if (element instanceof Short) {
              valueBuilder.setType(Common.Rep.SHORT).setNumberValue(((Short) element).longValue());
            } else if (element instanceof Integer) {
              valueBuilder
                  .setType(Common.Rep.INTEGER)
                  .setNumberValue(((Integer) element).longValue());
            } else if (element instanceof Long) {
              valueBuilder.setType(Common.Rep.LONG).setNumberValue((Long) element);
            } else if (element instanceof Double) {
              valueBuilder
                  .setType(Common.Rep.DOUBLE)
                  .setDoubleValue(((Double) element).doubleValue());
            } else if (element instanceof Float) {
              valueBuilder.setType(Common.Rep.FLOAT).setNumberValue(((Float) element).longValue());
            } else if (element instanceof BigDecimal) {
              valueBuilder
                  .setType(Common.Rep.NUMBER)
                  .setDoubleValue(((BigDecimal) element).doubleValue());
              // Strings
            } else if (element instanceof String) {
              valueBuilder.setType(Common.Rep.STRING).setStringValue((String) element);
            } else if (element instanceof Character) {
              valueBuilder
                  .setType(Common.Rep.CHARACTER)
                  .setStringValue(((Character) element).toString());
              // Bytes
            } else if (element instanceof byte[]) {
              valueBuilder
                  .setType(Common.Rep.BYTE_STRING)
                  .setBytesValues(ByteString.copyFrom((byte[]) element));
              // Boolean
            } else if (element instanceof Boolean) {
              valueBuilder.setType(Common.Rep.BOOLEAN).setBoolValue((boolean) element);
            } else if (null == element) {
              valueBuilder.setType(Common.Rep.NULL);
              // Unhandled
            } else {
              throw new RuntimeException("Unhandled type in Frame: " + element.getClass());
            }

            // Add value to row
            rowBuilder.addValue(valueBuilder.build());
          }

          // Collect all rows
          builder.addRows(rowBuilder.build());
        } else {
          // Can a "row" be a primitive? A struct? Only an Array?
          throw new RuntimeException("Only arrays are supported");
        }
      }

      return builder.build();
    }