def rows_to_columns(data, schema=None):
    """
    :param data: array of objects
    :param schema: Known schema, will be extended to include all properties found in data
    :return: Table
    """
    if not schema:
        schema = SchemaTree()
    all_schema = schema
    all_leaves = schema.leaves
    values = {full_name: [] for full_name in all_leaves}
    reps = {full_name: [] for full_name in all_leaves}
    defs = {full_name: [] for full_name in all_leaves}

    def _none_to_column(schema, path, rep_level, def_level):
        for full_path in all_schema.leaves:
            if startswith_field(full_path, path):
                reps[full_path].append(rep_level)
                defs[full_path].append(def_level)

    def _value_to_column(value, schema, path, counters, def_level):
        ptype = type(value)
        ntype, dtype, ltype, jtype, itype, byte_width = python_type_to_all_types[ptype]

        if jtype is NESTED:
            if schema.element.repetition_type != REPEATED:
                Log.error("Expecting {{path|quote}} to be repeated", path=path)

            new_path = path
            if not value:
                _none_to_column(schema, new_path, get_rep_level(counters), def_level)
            else:
                try:
                    new_schema = schema.more.get('.')

                    if not new_schema:
                        if schema.locked:
                            # DEFAULT TO REQUIRED ENTRIES
                            new_schema = schema
                            schema.element.repetition_type = REQUIRED
                        else:
                            new_path = path
                            new_value = value[0]
                            ptype = type(new_value)
                            new_schema = schema.add(
                                new_path,
                                OPTIONAL,
                                ptype
                            )
                            if new_value is None or python_type_to_json_type[ptype] in PRIMITIVE:
                                values[new_path] = []
                                reps[new_path] = [0] * counters[0]
                                defs[new_path] = [0] * counters[0]
                    for k, new_value in enumerate(value):
                        new_counters = counters + (k,)
                        _value_to_column(new_value, new_schema, new_path, new_counters, def_level+1)
                finally:
                    schema.element.repetition_type = REPEATED
        elif jtype is OBJECT:
            if value is None:
                if schema.element.repetition_type == REQUIRED:
                    Log.error("{{path|quote}} is required", path=path)
                _none_to_column(schema, path, get_rep_level(counters), def_level)
            else:
                if schema.element.repetition_type == REPEATED:
                    Log.error("Expecting {{path|quote}} to be repeated", path=path)

                if schema.element.repetition_type == REQUIRED:
                    new_def_level = def_level
                else:
                    counters = counters + (0,)
                    new_def_level = def_level+1

                for name, sub_schema in schema.more.items():
                    new_path = concat_field(path, name)
                    new_value = value.get(name, None)
                    _value_to_column(new_value, sub_schema, new_path, counters, new_def_level)

                for name in set(value.keys()) - set(schema.more.keys()):
                    if schema.locked:
                        Log.error("{{path}} is not allowed in the schema", path=path)
                    new_path = concat_field(path, name)
                    new_value = value.get(name, None)
                    ptype = type(new_value)
                    sub_schema = schema.add(
                        new_path,
                        REPEATED if isinstance(new_value, list) else OPTIONAL,
                        ptype
                    )
                    if python_type_to_json_type[ptype] in PRIMITIVE:
                        values[new_path] = []
                        reps[new_path] = [0] * counters[0]
                        defs[new_path] = [0] * counters[0]
                    _value_to_column(new_value, sub_schema, new_path, counters, new_def_level)
        else:
            if jtype is STRING:
                value = value.encode('utf8')
            merge_schema(schema, path, value)
            values[path].append(value)
            if schema.element.repetition_type == REQUIRED:
                reps[path].append(get_rep_level(counters))
                defs[path].append(def_level)
            else:
                reps[path].append(get_rep_level(counters))
                defs[path].append(def_level + 1)

    for rownum, new_value in enumerate(data):
        try:
            _value_to_column(new_value, schema, '.', (rownum,), 0)
        except Exception as e:
            Log.error("can not encode {{row|json}}", row=new_value, cause=e)

    return Table(values, reps, defs, len(data), schema)
:param data: array of objects
    :param schema: Known schema, will be extended to include all properties found in data
    :return: Table