using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace UniJSON { /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5 /// public class JsonObjectValidator : IJsonSchemaValidator { /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.1 /// public int MaxProperties { get; set; } /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.2 /// public int MinProperties { get; set; } HashSet m_required = new HashSet(); /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.3 /// public HashSet Required { get { return m_required; } } Dictionary m_props; /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.4 /// public Dictionary Properties { get { if (m_props == null) { m_props = new Dictionary(); } return m_props; } } /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.5 /// public string PatternProperties { get; private set; } /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.6 /// public JsonSchema AdditionalProperties { get; set; } Dictionary m_dependencies; /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.7 /// public Dictionary Dependencies { get { if (m_dependencies == null) { m_dependencies = new Dictionary(); } return m_dependencies; } } public void AddProperty(IFileSystemAccessor fs, string key, ListTreeNode value) { var sub = new JsonSchema(); sub.Parse(fs, value, key); if (Properties.ContainsKey(key)) { if (sub.Validator != null) { Properties[key].Validator.Merge(sub.Validator); } } else { Properties.Add(key, sub); } } public override int GetHashCode() { return 6; } public override bool Equals(object obj) { var rhs = obj as JsonObjectValidator; if (rhs == null) { return false; } if (Properties.Count != rhs.Properties.Count) { return false; } foreach (var pair in Properties) { JsonSchema value; if (rhs.Properties.TryGetValue(pair.Key, out value)) { #if true if (!value.Equals(pair.Value)) { Console.WriteLine(string.Format("{0} is not equals", pair.Key)); var l = pair.Value.Validator; var r = value.Validator; return false; } #else // key name match return true; #endif } else { return false; } } if (Required.Count != rhs.Required.Count) { return false; } if (!Required.OrderBy(x => x).SequenceEqual(rhs.Required.OrderBy(x => x))) { return false; } if (Dependencies.Count != rhs.Dependencies.Count) { return false; } foreach (var kv in Dependencies) { if (!kv.Value.OrderBy(x => x).SequenceEqual(rhs.Dependencies[kv.Key].OrderBy(x => x))) { return false; } } if (AdditionalProperties == null && rhs.AdditionalProperties == null) { // ok } else if (AdditionalProperties == null) { return false; } else if (rhs.AdditionalProperties == null) { return false; } else { if (!AdditionalProperties.Equals(rhs.AdditionalProperties)) { return false; } } return true; } public void Merge(IJsonSchemaValidator obj) { var rhs = obj as JsonObjectValidator; if (rhs == null) { throw new ArgumentException(); } foreach (var x in rhs.Properties) { if (this.Properties.ContainsKey(x.Key)) { this.Properties[x.Key] = x.Value; } else { this.Properties.Add(x.Key, x.Value); } } foreach (var x in rhs.Required) { this.Required.Add(x); } if (rhs.AdditionalProperties != null) { if (AdditionalProperties != null) { throw new NotImplementedException(); } AdditionalProperties = rhs.AdditionalProperties; } } public bool FromJsonSchema(IFileSystemAccessor fs, string key, ListTreeNode value) { switch (key) { case "maxProperties": MaxProperties = value.GetInt32(); return true; case "minProperties": MinProperties = value.GetInt32(); return true; case "required": { foreach (var req in value.ArrayItems()) { m_required.Add(req.GetString()); } } return true; case "properties": { foreach (var prop in value.ObjectItems()) { AddProperty(fs, prop.Key.GetString(), prop.Value); } } return true; case "patternProperties": PatternProperties = value.GetString(); return true; case "additionalProperties": { var sub = new JsonSchema(); sub.Parse(fs, value, "additionalProperties"); AdditionalProperties = sub; } return true; case "dependencies": { foreach (var kv in value.ObjectItems()) { Dependencies.Add(kv.Key.GetString(), kv.Value.ArrayItems().Select(x => x.GetString()).ToArray()); } } return true; case "propertyNames": return true; } return false; } public void ToJsonSchema(IFormatter f) { f.Key("type"); f.Value("object"); if (Properties.Count > 0) { f.Key("properties"); f.BeginMap(Properties.Count); foreach (var kv in Properties) { f.Key(kv.Key); kv.Value.ToJson(f); } f.EndMap(); } } static class GenericFieldView { public static FieldInfo[] GetFields() { var t = typeof(T); return t.GetFields(BindingFlags.Instance | BindingFlags.Public); } public static void CreateFieldProcessors( Func creator, Dictionary processors ) { foreach (var fi in GetFields()) { processors.Add(fi.Name, creator(fi)); } } } internal class ValidationResult { public bool IsIgnorable; public JsonSchemaValidationException Ex; } public static class GenericValidator { class ObjectValidator { delegate JsonSchemaValidationException FieldValidator( JsonSchema s, JsonSchemaValidationContext c, T o, out bool isIgnorable); Dictionary m_validators; static FieldValidator CreateFieldValidator(FieldInfo fi) { var mi = typeof(ObjectValidator).GetMethod("_CreateFieldValidator", BindingFlags.Static | BindingFlags.NonPublic) ; var g = mi.MakeGenericMethod(fi.FieldType); return GenericInvokeCallFactory.StaticFunc(g)(fi); } static FieldValidator _CreateFieldValidator(FieldInfo fi) { var getter = (Func)((t) => (U)fi.GetValue(t)); return (JsonSchema s, JsonSchemaValidationContext c, T o, out bool isIgnorable) => { var v = s.Validator; using (c.Push(fi.Name)) { var field = getter(o); var ex = v.Validate(c, field); isIgnorable = ex != null && s.IsExplicitlyIgnorableValue(field); return ex; } }; } public ObjectValidator() { var validators = new Dictionary(); GenericFieldView.CreateFieldProcessors( CreateFieldValidator, validators); m_validators = validators; } public JsonSchemaValidationException ValidateProperty( HashSet required, KeyValuePair property, JsonSchemaValidationContext c, T o, out bool isIgnorable ) { var fieldName = property.Key; var schema = property.Value; isIgnorable = false; FieldValidator fv; if (m_validators.TryGetValue(fieldName, out fv)) { var isRequired = required != null && required.Contains(fieldName); bool isMemberIgnorable; var ex = fv(schema, c, o, out isMemberIgnorable); if (ex != null) { isIgnorable = !isRequired && isMemberIgnorable; if (isRequired // required fields must be checked || c.EnableDiagnosisForNotRequiredFields) { return ex; } } } return null; } public JsonSchemaValidationException Validate( HashSet required, Dictionary properties, JsonSchemaValidationContext c, T o) { foreach (var kv in properties) { bool isIgnorable; var ex = ValidateProperty(required, kv, c, o, out isIgnorable); if (ex != null && !isIgnorable) { return ex; } } return null; } public void ValidationResults (HashSet required, Dictionary properties, JsonSchemaValidationContext c, T o, Dictionary results) { foreach (var kv in properties) { bool isIgnorable; var ex = ValidateProperty(required, kv, c, o, out isIgnorable); results.Add(kv.Key, new ValidationResult { IsIgnorable = isIgnorable, Ex = ex, }); } } } static ObjectValidator s_validator; static void prepareValidator() { if (s_validator == null) { s_validator = new ObjectValidator(); } } public static JsonSchemaValidationException Validate(HashSet required, Dictionary properties, JsonSchemaValidationContext c, T o) { prepareValidator(); return s_validator.Validate(required, properties, c, o); } internal static void ValidationResults(HashSet required, Dictionary properties, JsonSchemaValidationContext c, T o, Dictionary results) { prepareValidator(); s_validator.ValidationResults(required, properties, c, o, results); } } public JsonSchemaValidationException Validate(JsonSchemaValidationContext c, T o) { if (o == null) { return new JsonSchemaValidationException(c, "null"); } if (Properties.Count < MinProperties) { return new JsonSchemaValidationException(c, "no properties"); } return GenericValidator.Validate(Required, Properties, c, o); } static class GenericSerializer { class Serializer { delegate void FieldSerializer(JsonSchema s, JsonSchemaValidationContext c, IFormatter f, T o, Dictionary vRes, string[] deps); Dictionary m_serializers; static FieldSerializer CreateFieldSerializer(FieldInfo fi) { var mi = typeof(Serializer).GetMethod("_CreateFieldSerializer", BindingFlags.Static | BindingFlags.NonPublic); var g = mi.MakeGenericMethod(fi.FieldType); return GenericInvokeCallFactory.StaticFunc(g)(fi); } static FieldSerializer _CreateFieldSerializer(FieldInfo fi) { Func getter = t => { return (U)fi.GetValue(t); }; return (s, c, f, o, vRes, deps) => { var v = s.Validator; var field = getter(o); if (vRes[fi.Name].Ex != null) { return; } if (deps != null) { foreach(var dep in deps) { if (vRes[dep].Ex != null) { return; } } } f.Key(fi.Name); v.Serialize(f, c, field); }; } public Serializer() { var serializers = new Dictionary(); GenericFieldView.CreateFieldProcessors( CreateFieldSerializer, serializers); m_serializers = serializers; } public void Serialize(JsonObjectValidator objectValidator, IFormatter f, JsonSchemaValidationContext c, T o) { // Validates fields var validationResults = new Dictionary(); GenericValidator.ValidationResults( objectValidator.Required, objectValidator.Properties, c, o, validationResults); // Serialize fields f.BeginMap(objectValidator.Properties.Count()); foreach (var property in objectValidator.Properties) { var fieldName = property.Key; var schema = property.Value; string[] deps = null; objectValidator.Dependencies.TryGetValue(fieldName, out deps); FieldSerializer fs; if (m_serializers.TryGetValue(fieldName, out fs)) { fs(schema, c, f, o, validationResults, deps); } } f.EndMap(); } } static FieldInfo[] s_fields; static Serializer s_serializer; public static void Serialize(JsonObjectValidator objectValidator, IFormatter f, JsonSchemaValidationContext c, T value) { if (s_serializer == null) { s_serializer = new Serializer(); } s_serializer.Serialize(objectValidator, f, c, value); } } public void Serialize(IFormatter f, JsonSchemaValidationContext c, T value) { GenericSerializer.Serialize(this, f, c, value); } public static class GenericDeserializer where S : IListTreeItem, IValue { delegate T Deserializer(ListTreeNode src); static Deserializer s_d; delegate void FieldSetter(ListTreeNode s, object o); static FieldSetter GetFieldDeserializer(FieldInfo fi) { return (s, o) => { var u = default(U); s.Deserialize(ref u); fi.SetValue(o, u); }; } public static U DeserializeField(JsonSchema prop, ListTreeNode s) { var u = default(U); prop.Validator.Deserialize(s, ref u); return u; } public static void Deserialize(ListTreeNode src, ref T dst, Dictionary props) { if (s_d == null) { var target = typeof(T); var fields = target.GetFields(BindingFlags.Instance | BindingFlags.Public); var fieldDeserializers = fields.ToDictionary(x => Utf8String.From(x.Name), x => { /* var mi = typeof(GenericDeserializer).GetMethod("GetFieldDeserializer", BindingFlags.Static | BindingFlags.NonPublic); var g = mi.MakeGenericMethod(x.FieldType); return (FieldSetter)g.Invoke(null, new object[] { x }); */ JsonSchema prop; if (!props.TryGetValue(x.Name, out prop)) { return null; } var mi = typeof(GenericDeserializer).GetMethod("DeserializeField", BindingFlags.Static | BindingFlags.Public); var g = mi.MakeGenericMethod(x.FieldType); return (FieldSetter)((s, o) => { var f = g.Invoke(null, new object[] { prop, s }); x.SetValue(o, f); }); }); s_d = (ListTreeNode s) => { if (!s.IsMap()) { throw new ArgumentException(s.Value.ValueType.ToString()); } // boxing var t = (object)Activator.CreateInstance(); foreach (var kv in s.ObjectItems()) { FieldSetter setter; if (fieldDeserializers.TryGetValue(kv.Key.GetUtf8String(), out setter)) { if (setter != null) { setter(kv.Value, t); } } } return (T)t; }; } dst = s_d(src); } } public void Deserialize(ListTreeNode src, ref U dst) where T : IListTreeItem, IValue { GenericDeserializer.Deserialize(src, ref dst, Properties); } } }