Get datatype from values passed as string

前端 未结 5 694
滥情空心
滥情空心 2021-02-04 12:16

I am writing a framework that will connect to many different data source types and return values from these sources. The easy ones are SQL, Access and Oracle. The tougher ones a

5条回答
  •  不思量自难忘°
    2021-02-04 12:44

    Since Dimi put a bounty and needs more "modern" solution, I'll try to provide one. First, what do we need from reasonable class which converts strings to different stuff?

    Reasonable behavior with basic types.

    Respect culture info, especially when converting numbers and dates.

    Ability to extend logic with custom converters if necessary.

    As a bonus avoid long "if" chains since they are quite error-prone.

    public class StringConverter {
        // delegate for TryParse(string, out T)
        public delegate bool TypedConvertDelegate(string value, out T result);
        // delegate for TryParse(string, out object)
        private delegate bool UntypedConvertDelegate(string value, out object result);        
        private readonly List _converters = new List();
        // default converter, lazyly initialized
        private static readonly Lazy _default = new Lazy(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.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(Predicate match, Func 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(Regex match, Func 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(TypedConvertDelegate constructor) {
            // create converter from typed TryParse(string, out T) function
            _converters.Add(FromTryPattern(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(TypedConvertDelegate inner) {
            return (string value, out object result) => {
                T tmp;
                if (inner.Invoke(value, out tmp)) {
                    result = tmp;
                    return true;
                }
                else {
                    result = null;
                    return false;
                }
            };
        }
    }
    

    Use like this:

    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(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(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; }
    }
    

提交回复
热议问题