/// <summary> /// Extracts VW features from given type based on JSON.NET annotation. Basic structure: /// /// { /// _label: { ... }, // SimpleLabel or ContextualBanditLabel /// ns1: { // Complex types denote namespaces. Property name becomes namespace name. /// feature1: 5, // Primitive types denote features /// ... /// }, /// ns2 : { ... }, // another namespace /// feature2: true // Top-level primitive property becomes feature in default namespace. /// } /// </summary> internal static Schema CreateSchema(Type type, PropertyConfiguration propertyConfiguration) { var exampleMemberSerialization = GetMemberSerialiation(type); // find all feature properties under namespace properties var namespaceFeatures = from ns in type.GetProperties() // removing any JsonIgnore properties where !ns.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let nsAttr = (JsonPropertyAttribute)ns.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() let nsIsMarkedWithJsonConverter = ns.GetCustomAttribute(typeof(JsonConverterAttribute), true) is JsonConverterAttribute where !IsDictType(ns.PropertyType) && !IsTypeSupported(ns.PropertyType) && !nsIsMarkedWithJsonConverter && // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && nsAttr != null)) let namespaceRawValue = nsAttr != null && nsAttr.PropertyName != null ? nsAttr.PropertyName : ns.Name // filter all aux properties where !namespaceRawValue.StartsWith(propertyConfiguration.FeatureIgnorePrefix, StringComparison.Ordinal) let featureGroup = namespaceRawValue[0] let namespaceValue = namespaceRawValue.Length > 1 ? namespaceRawValue.Substring(1) : null let namespaceMemberSerialization = GetMemberSerialiation(ns.PropertyType) from p in ns.PropertyType.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() let isMarkedWithJsonConverter = p.GetCustomAttribute(typeof(JsonConverterAttribute), true) is JsonConverterAttribute where (IsTypeSupported(p.PropertyType) || isMarkedWithJsonConverter) && // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name let isTextProperty = name == propertyConfiguration.TextProperty // filter all aux properties where isTextProperty || !name.StartsWith(propertyConfiguration.FeatureIgnorePrefix, StringComparison.Ordinal) select new FeatureExpression( featureType: isMarkedWithJsonConverter ? typeof(VowpalWabbitJsonSerializable) : p.PropertyType, name: name, // CODE example.NamespaceProperty.FeatureProperty valueExpressionFactory: CreateValueExpressionFactory(ns, p), // Note: default to string escaping stringProcessing: isTextProperty ? StringProcessing.Split : StringProcessing.EscapeAndIncludeName, // CODE example != null // CODE example.NamespaceProperty != null valueValidExpressionFactories: new List <Func <Expression, Expression> > { valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)), valueExpression => Expression.NotEqual(Expression.Property(valueExpression, ns), Expression.Constant(null)) }, @namespace: namespaceValue, featureGroup: featureGroup); // find all top-level feature properties for the default namespace var defaultNamespaceFeatures = from p in type.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name // filter all aux properties, except for special props where propertyConfiguration.IsSpecialProperty(name) || !name.StartsWith(propertyConfiguration.FeatureIgnorePrefix, StringComparison.Ordinal) // filtering labels for now where name != propertyConfiguration.LabelProperty let isMarkedWithJsonConverter = p.GetCustomAttribute(typeof(JsonConverterAttribute), true) is JsonConverterAttribute where IsTypeSupported(p.PropertyType) || // _multi can be any list type that JSON.NET supports name == propertyConfiguration.MultiProperty || isMarkedWithJsonConverter || // labels must be ILabel or string // Note: from the JSON side they actually can be anything that serializes to the same properties as ILabel implementors (name == propertyConfiguration.LabelProperty && (typeof(ILabel).IsAssignableFrom(p.PropertyType) || p.PropertyType == typeof(string))) select new FeatureExpression( featureType: isMarkedWithJsonConverter ? typeof(VowpalWabbitJsonSerializable) : p.PropertyType, name: name, // CODE example.FeatureProperty valueExpressionFactory: CreateValueExpressionFactory(null, p), // Note: default to string escaping stringProcessing: name == propertyConfiguration.TextProperty ? StringProcessing.Split : StringProcessing.EscapeAndIncludeName, // CODE example != null valueValidExpressionFactories: new List <Func <Expression, Expression> > { valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)) }, @namespace: p.PropertyType.IsArray && name.Length > 1 ? name.Substring(1) : null, featureGroup: p.PropertyType.IsArray && name.Length > 0 ? name[0] : VowpalWabbitConstants.DefaultNamespace); // find all top-level dictionaries var topLevelDictionaries = from p in type.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) where IsDictType(p.PropertyType) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name let namespaceRawValue = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name // filter all aux properties where !namespaceRawValue.StartsWith(propertyConfiguration.FeatureIgnorePrefix, StringComparison.Ordinal) let featureGroup = namespaceRawValue[0] let namespaceValue = namespaceRawValue.Length > 1 ? namespaceRawValue.Substring(1) : null select new FeatureExpression( featureType: p.PropertyType, name: name, // CODE example.FeatureProperty valueExpressionFactory: CreateValueExpressionFactory(null, p), // CODE example != null valueValidExpressionFactories: new List <Func <Expression, Expression> > { valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)) }, @namespace: namespaceValue, featureGroup: featureGroup); // find label var labelProperties = from p in type.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name // filtering labels for now where name == propertyConfiguration.LabelProperty where // labels must be ILabel or string // Note: from the JSON side they actually can be anything that serializes to the same properties as ILabel implementors (name == propertyConfiguration.LabelProperty && (typeof(ILabel).IsAssignableFrom(p.PropertyType) || p.PropertyType == typeof(string))) select new LabelExpression { LabelType = p.PropertyType, Name = name, // CODE example.Label ValueExpressionFactory = valueExpression => Expression.Property(valueExpression, p), // CODE example != null ValueValidExpressionFactories = new List <Func <Expression, Expression> > { valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)) } }; // TODO: _label_ and _labelIndex is not supported return(new Schema { Label = labelProperties.FirstOrDefault(), Features = namespaceFeatures .Union(defaultNamespaceFeatures) .Union(topLevelDictionaries).ToList() }); }
/// <summary> /// Extracts VW features from given type based on JSON.NET annotation. Basic structure: /// /// { /// _label: { ... }, // SimpleLabel or ContextualBanditLabel /// ns1: { // Complex types denote namespaces. Property name becomes namespace name. /// feature1: 5, // Primitive types denote features /// ... /// }, /// ns2 : { ... }, // another namespace /// feature2: true // Top-level primitive property becomes feature in default namespace. /// } /// </summary> internal static Schema CreateSchema(Type type, PropertyConfiguration propertyConfiguration) { var exampleMemberSerialization = GetMemberSerialiation(type); // find all feature properties under namespace properties var namespaceFeatures = from ns in type.GetProperties() // removing any JsonIgnore properties where !ns.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let nsAttr = (JsonPropertyAttribute)ns.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where !IsFeatureTypeSupported(ns.PropertyType) && // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && nsAttr != null)) let namespaceRawValue = nsAttr != null && nsAttr.PropertyName != null ? nsAttr.PropertyName : ns.Name // filter all aux properties where !namespaceRawValue.StartsWith(propertyConfiguration.FeatureIgnorePrefix) let featureGroup = namespaceRawValue[0] let namespaceValue = namespaceRawValue.Length > 1 ? namespaceRawValue.Substring(1) : null let namespaceMemberSerialization = GetMemberSerialiation(ns.PropertyType) from p in ns.PropertyType.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where IsFeatureTypeSupported(p.PropertyType) && // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name let isTextProperty = name == propertyConfiguration.TextProperty // filter all aux properties where isTextProperty || !name.StartsWith(propertyConfiguration.FeatureIgnorePrefix) select new FeatureExpression( featureType: p.PropertyType, name: name, // CODE example.NamespaceProperty.FeatureProperty valueExpressionFactory: valueExpression => Expression.Property(Expression.Property(valueExpression, ns), p), // Note: default to string escaping stringProcessing: isTextProperty ? StringProcessing.Split : StringProcessing.EscapeAndIncludeName, // CODE example != null // CODE example.NamespaceProperty != null valueValidExpressionFactories: new List<Func<Expression, Expression>>{ valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)), valueExpression => Expression.NotEqual(Expression.Property(valueExpression, ns), Expression.Constant(null)) }, @namespace: namespaceValue, featureGroup: featureGroup); // find all top-level feature properties for the default namespace var defaultNamespaceFeatures = from p in type.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name // filter all aux properties, except for special props where propertyConfiguration.IsSpecialProperty(name) || !name.StartsWith(propertyConfiguration.FeatureIgnorePrefix) // filterint labels for now where name != propertyConfiguration.LabelProperty where IsFeatureTypeSupported(p.PropertyType) || // _multi can be any list type that JSON.NET supports name == propertyConfiguration.MultiProperty || // labels must be ILabel or string // Note: from the JSON side they actually can be anything that serializes to the same properties as ILabel implementors (name == propertyConfiguration.LabelProperty && (typeof(ILabel).IsAssignableFrom(p.PropertyType) || p.PropertyType == typeof(string))) select new FeatureExpression( featureType: p.PropertyType, name: name, // CODE example.FeatureProperty valueExpressionFactory: valueExpression => Expression.Property(valueExpression, p), // Note: default to string escaping stringProcessing: name == propertyConfiguration.TextProperty ? StringProcessing.Split : StringProcessing.EscapeAndIncludeName, // CODE example != null valueValidExpressionFactories: new List<Func<Expression, Expression>>{ valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)) }, featureGroup: VowpalWabbitConstants.DefaultNamespace); // find label var labelProperties = from p in type.GetProperties() // removing any JsonIgnore properties where !p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Any() let attr = (JsonPropertyAttribute)p.GetCustomAttributes(typeof(JsonPropertyAttribute), true).FirstOrDefault() where // model OptIn/OptOut (exampleMemberSerialization == MemberSerialization.OptOut || (exampleMemberSerialization == MemberSerialization.OptIn && attr != null)) let name = attr != null && attr.PropertyName != null ? attr.PropertyName : p.Name // filterint labels for now where name == propertyConfiguration.LabelProperty where // labels must be ILabel or string // Note: from the JSON side they actually can be anything that serializes to the same properties as ILabel implementors (name == propertyConfiguration.LabelProperty && (typeof(ILabel).IsAssignableFrom(p.PropertyType) || p.PropertyType == typeof(string))) select new LabelExpression { LabelType = p.PropertyType, Name = name, // CODE example.Label ValueExpressionFactory = valueExpression => Expression.Property(valueExpression, p), // CODE example != null ValueValidExpressionFactories = new List<Func<Expression, Expression>>{ valueExpression => Expression.NotEqual(valueExpression, Expression.Constant(null)) } }; return new Schema { Label = labelProperties.FirstOrDefault(), Features = namespaceFeatures.Union(defaultNamespaceFeatures).ToList() }; }