Получить тип данных из значений, переданных в виде строки - PullRequest
7 голосов
/ 15 марта 2011

Я пишу каркас, который будет подключаться к множеству различных типов источников данных и возвращать значения из этих источников. Легкими являются SQL, Access и Oracle. Более сложными являются Sharepoint, CSV.

Если я возвращаю значения из текстовых источников, я бы хотел определить тип данных.

Поскольку CSV - это весь текст, метаданные для опроса отсутствуют, мне нужно как-то проанализировать данные, чтобы определить тип данных.

Пример:

Список "true", "true", "false", "false" будет логическим
Список «1», «0», «1», «0» будет логическим
Список "1", "4", "-10", "500" будет целым числом
Список «15,2», «2015.5896», «1.0245», «500» будет удвоен
Список «2001/01/01», «2010/05/29 12:00», «1989/12/25 10:34:21» будет дата-время

Он основан на /536161/c-somnevays-nahodya-tip-dannyh#536166

object ParseString(string str)
{

   Int32 intValue;
   Int64 bigintValue;
   double doubleValue;
   bool boolValue;
   DateTime dateValue;        

// Place checks higher in if-else statement to give higher priority to type.

if (Int32.TryParse(str, out intValue))
    return intValue;
else if (Int64.TryParse(str, out bigintValue))
    return bigintValue;
else if (double.TryParse(str, out doubleValue))
    return doubleValue;       
else if (bool.TryParse(str, out boolValue))
    return boolValue;
else if (DateTime.TryParse(str, out dateValue))
    return dateValue;
else return str;

}

Редактировать: мне нужно только обслуживать следующее:

BIT  
DATETIME  
INT  
NVARCHAR(255)  
NVARCHAR(MAX)  
BIGINT  
DECIMAL(36, 17)  

Видите ли вы какое-либо возможное улучшение приоритета?

Ответы [ 5 ]

14 голосов
/ 16 марта 2011

Я нашел следующее решение, которое работает:

enum dataType
    {
        System_Boolean = 0,
        System_Int32 = 1,
        System_Int64 = 2,
        System_Double = 3,
        System_DateTime = 4,
        System_String = 5
    }

    private dataType ParseString(string str)
    {

        bool boolValue;
        Int32 intValue;
        Int64 bigintValue;
        double doubleValue;
        DateTime dateValue;

        // Place checks higher in if-else statement to give higher priority to type.

        if (bool.TryParse(str, out boolValue))
            return dataType.System_Boolean;
        else if (Int32.TryParse(str, out intValue))
            return dataType.System_Int32;
        else if (Int64.TryParse(str, out bigintValue))
            return dataType.System_Int64;
        else if (double.TryParse(str, out doubleValue))
            return dataType.System_Double;
        else if (DateTime.TryParse(str, out dateValue))
            return dataType.System_DateTime;
        else return dataType.System_String;

    }


    /// <summary>
    /// Gets the datatype for the Datacolumn column
    /// </summary>
    /// <param name="column">Datacolumn to get datatype of</param>
    /// <param name="dt">DataTable to get datatype from</param>
    /// <param name="colSize">ref value to return size for string type</param>
    /// <returns></returns>
    public Type GetColumnType(DataColumn column, DataTable dt, ref int colSize)
    {

        Type T;
        DataView dv = new DataView(dt);
        //get smallest and largest values
        string colName = column.ColumnName;

        dv.RowFilter = "[" + colName + "] = MIN([" + colName + "])";
        DataTable dtRange = dv.ToTable();
        string strMinValue = dtRange.Rows[0][column.ColumnName].ToString();
        int minValueLevel = (int)ParseString(strMinValue);

        dv.RowFilter = "[" + colName + "] = MAX([" + colName + "])";
        dtRange = dv.ToTable();
        string strMaxValue = dtRange.Rows[0][column.ColumnName].ToString();
        int maxValueLevel = (int)ParseString(strMaxValue);
        colSize = strMaxValue.Length;

        //get max typelevel of first n to 50 rows
        int sampleSize = Math.Max(dt.Rows.Count, 50);
        int maxLevel = Math.Max(minValueLevel, maxValueLevel);

        for (int i = 0; i < sampleSize; i++)
        {
            maxLevel = Math.Max((int)ParseString(dt.Rows[i][column].ToString()), maxLevel);
        }

        string enumCheck = ((dataType)maxLevel).ToString();
        T = Type.GetType(enumCheck.Replace('_', '.'));

        //if typelevel = int32 check for bit only data & cast to bool
        if (maxLevel == 1 && Convert.ToInt32(strMinValue) == 0 && Convert.ToInt32(strMaxValue) == 1)
        {
            T = Type.GetType("System.Boolean");
        }

        if (maxLevel != 5) colSize = -1;


        return T;
    }
10 голосов
/ 05 мая 2016

Так как Дими ставит награду и нуждается в более «современном» решении, я постараюсь предоставить его.Во-первых, что нам нужно от разумного класса, который конвертирует строки в разные вещи?

Разумное поведение с базовыми типами.

Уважайте информацию о культуре, особенно при конвертации чисел и дат.

Возможность расширять логику с помощью пользовательских преобразователей при необходимости.

В качестве бонуса избегайте длинных цепочек «если», поскольку они весьма подвержены ошибкам.

public class StringConverter {
    // delegate for TryParse(string, out T)
    public delegate bool TypedConvertDelegate<T>(string value, out T result);
    // delegate for TryParse(string, out object)
    private delegate bool UntypedConvertDelegate(string value, out object result);        
    private readonly List<UntypedConvertDelegate> _converters = new List<UntypedConvertDelegate>();
    // default converter, lazyly initialized
    private static readonly Lazy<StringConverter> _default = new Lazy<StringConverter>(CreateDefault, true);

    public static StringConverter Default => _default.Value;

    private static StringConverter CreateDefault() {
        var d = new StringConverter();
        // add reasonable default converters for common .NET types. Don't forget to take culture into account, that's
        // important when parsing numbers\dates.
        d.AddConverter<bool>(bool.TryParse);
        d.AddConverter((string value, out byte result) => byte.TryParse(value, NumberStyles.Integer, d.Culture, out result));
        d.AddConverter((string value, out short result) => short.TryParse(value, NumberStyles.Integer, d.Culture, out result));
        d.AddConverter((string value, out int result) => int.TryParse(value, NumberStyles.Integer, d.Culture, out result));
        d.AddConverter((string value, out long result) => long.TryParse(value, NumberStyles.Integer, d.Culture, out result));
        d.AddConverter((string value, out float result) => float.TryParse(value, NumberStyles.Number, d.Culture, out result));
        d.AddConverter((string value, out double result) => double.TryParse(value, NumberStyles.Number, d.Culture, out result));
        d.AddConverter((string value, out DateTime result) => DateTime.TryParse(value, d.Culture, DateTimeStyles.None, out result));
        return d;
    }

    //
    public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;

    public void AddConverter<T>(Predicate<string> match, Func<string, T> converter) {
        // create converter from match predicate and convert function
        _converters.Add((string value, out object result) => {
            if (match(value)) {
                result = converter(value);
                return true;
            }
            result = null;
            return false;
        });
    }

    public void AddConverter<T>(Regex match, Func<string, T> converter) {
        // create converter from match regex and convert function
        _converters.Add((string value, out object result) => {
            if (match.IsMatch(value)) {
                result = converter(value);
                return true;
            }
            result = null;
            return false;
        });
    }

    public void AddConverter<T>(TypedConvertDelegate<T> constructor) {
        // create converter from typed TryParse(string, out T) function
        _converters.Add(FromTryPattern<T>(constructor));
    }

    public bool TryConvert(string value, out object result) {
        if (this != Default) {
            // if this is not a default converter - first try convert with default
            if (Default.TryConvert(value, out result))
                return true;
        }
        // then use local converters. Any will return after the first match
        object tmp = null;
        bool anyMatch = _converters.Any(c => c(value, out tmp));
        result = tmp;
        return anyMatch;
    }

    private static UntypedConvertDelegate FromTryPattern<T>(TypedConvertDelegate<T> inner) {
        return (string value, out object result) => {
            T tmp;
            if (inner.Invoke(value, out tmp)) {
                result = tmp;
                return true;
            }
            else {
                result = null;
                return false;
            }
        };
    }
}

Используйте вот так:

static void Main(string[] args) {
    // set culture to invariant
    StringConverter.Default.Culture = CultureInfo.InvariantCulture;
    // add custom converter to default, it will match strings starting with CUSTOM: and return MyCustomClass
    StringConverter.Default.AddConverter(c => c.StartsWith("CUSTOM:"), c => new MyCustomClass(c));
    var items = new[] {"1", "4343434343", "3.33", "true", "false", "2014-10-10 22:00:00", "CUSTOM: something"};
    foreach (var item in items) {
        object result;
        if (StringConverter.Default.TryConvert(item, out result)) {
            Console.WriteLine(result);
        }
    }
    // create new non-default converter
    var localConverter = new StringConverter();
    // add custom converter to parse json which matches schema for MySecondCustomClass
    localConverter.AddConverter((string value, out MySecondCustomClass result) => TryParseJson(value, @"{'value': {'type': 'string'}}", out result));
    {
        object result;
        // check if that works
        if (localConverter.TryConvert("{value: \"Some value\"}", out result)) {
            Console.WriteLine(((MySecondCustomClass) result).Value);
        }
    }
    Console.ReadKey();
}

static bool TryParseJson<T>(string json, string rawSchema, out T result) where T : new() {
    // we are using Newtonsoft.Json here
    var parsedSchema = JsonSchema.Parse(rawSchema);
    JObject jObject = JObject.Parse(json);
    if (jObject.IsValid(parsedSchema)) {
        result = JsonConvert.DeserializeObject<T>(json);
        return true;
    }
    else {
        result = default(T);
        return false;
    }
}

class MyCustomClass {
    public MyCustomClass(string value) {
        this.Value = value;
    }

    public string Value { get; private set; }
}

public class MySecondCustomClass {
    public string Value { get; set; }
}
3 голосов
/ 16 марта 2011
    List<Type> types = new List<Type>(new Type[] {
        typeof(Boolean)
        , typeof(int)
        , typeof(double)
        , typeof(DateTime)
    });
    string t = "true";
    object retu;
    foreach (Type type in types)
    {
        TypeConverter tc = TypeDescriptor.GetConverter(type);
        if (tc != null)
        {
            try
            {
                object obj = tc.ConvertFromString(t); // your return value;
            }
            catch (Exception)
            {
                continue;
            }
        }
    }
1 голос
/ 15 марта 2011

Начинать с самых узких типов и работать в направлении самых широких, возможно, не самый лучший подход.Если бы я знал что-либо о данных, я бы начал с наиболее часто встречающегося типа и работал бы в направлении наименьшего.Если бы не знал об этом, я мог бы или не мог бы провести некоторое исследование, чтобы получить представление о том, что это может быть статистически, если это возможно.В противном случае я бы сделал предположение.Зачем проверять бит или дату и время раньше, если вы ожидаете, что они будут появляться один раз каждые 10 000 записей?

1 голос
/ 15 марта 2011

Было бы проще сохранить его в общем типе данных с помощью .ToInt16 (), .ToInt32 (), .ToBool () и т. Д.? Если вы напишите приложение, ожидающее int, и оно получит логическое значение, то оно не будет выполнено, поэтому лучше позволить программисту явным образом преобразовать в ожидаемый тип данных.

Проблема с вашим подходом заключается в том, что вы не знаете, будет ли строка, содержащая 0 в качестве первого элемента, содержать -100000 в качестве номера элемента 100. Это означает, что вы не сможете выполнить успешное преобразование, пока все строки не будут TryParsed by все разные типы данных. Очень дорогая операция!

Во всяком случае, я бы использовал предварительно скомпилированные регулярные выражения и / или собственную логику для обработки данных. Например, итерация всех строк для нахождения наибольшего / наименьшего числа, появления строки и т. Д.

...