using System; using System.Linq; using System.Collections.Generic; 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; } List m_required = new List(); /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.3 /// public List 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_depndencies; /// /// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.5.7 /// public Dictionary Dependencies { get { if (m_depndencies == null) { m_depndencies = new Dictionary(); } return m_depndencies; } } public void AddProperty(IFileSystemAccessor fs, string key, JsonNode value) { var sub = new JsonSchema(); sub.Parse(fs, value, key); if (Properties.ContainsKey(key)) { if (sub.Validator != null) { Properties[key].Validator.Assign(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 Assign(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 Parse(IFileSystemAccessor fs, string key, JsonNode 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, 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, kv.Value.ArrayItems.Select(x => x.GetString()).ToArray()); } } return true; case "propertyNames": return true; } return false; } public JsonSchemaValidationException Validate(JsonSchemaValidationContext c, object o) { if (o == null) { return new JsonSchemaValidationException(c, "null"); } if (Properties.Count == 0) { return new JsonSchemaValidationException(c, "no properties"); } if (Required != null) { foreach (var x in Required) { using (c.Push(x)) { var value = o.GetValueByKey(x); var ex = Properties[x].Validator.Validate(c, value); if (ex != null) { return ex; } } } } return null; } Dictionary m_validValueMap = new Dictionary(); public void Serialize(JsonFormatter f, JsonSchemaValidationContext c, Object o) { // validate properties m_validValueMap.Clear(); foreach (var kv in Properties) { var value = o.GetValueByKey(kv.Key); var v = kv.Value.Validator; using (c.Push(kv.Key)) { if (v != null && v.Validate(c, value) == null) { m_validValueMap.Add(kv.Key, value); } } } using (f.BeginMapDisposable()) { foreach (var kv in Properties) { object value; if (!m_validValueMap.TryGetValue(kv.Key, out value)) { continue; } string[] dependencies; if (Dependencies.TryGetValue(kv.Key, out dependencies)) { // check dependencies bool hasDependencies = true; foreach (var x in dependencies) { if (!m_validValueMap.ContainsKey(x)) { hasDependencies = false; break; } } if (!hasDependencies) { continue; } } // key f.Key(kv.Key); // value using (c.Push(kv.Key)) { kv.Value.Validator.Serialize(f, c, value); } } } } public void ToJson(JsonFormatter f) { f.Key("type"); f.Value("object"); if (Properties.Count > 0) { f.Key("properties"); f.BeginMap(); foreach (var kv in Properties) { f.Key(kv.Key); kv.Value.ToJson(f); } f.EndMap(); } } } }