UniVRM/Assets/VRM/UniJSON/Scripts/Json/JsonSchema.cs

446 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace UniJSON
{
public class JsonSchema : IEquatable<JsonSchema>
{
public string Schema; // http://json-schema.org/draft-04/schema
#region Annotations
string m_title;
public string Title
{
get { return m_title; }
private set
{
if (value == null)
{
m_title = "";
}
else
{
m_title = value.Trim();
}
}
}
string m_desc;
public string Description
{
get { return m_desc; }
private set
{
if (value == null)
{
m_desc = "";
}
else
{
m_desc = value.Trim();
}
}
}
public object Default
{
get;
private set;
}
#endregion
public IJsonSchemaValidator Validator { get; set; }
/// <summary>
/// Skip validator comparison
/// </summary>
public bool SkipComparison { get; set; }
public object ExplicitIgnorableValue { private get; set; }
public int ExplicitIgnorableItemLength { private get; set; }
public override string ToString()
{
return string.Format("<{0}>", Title);
}
public override int GetHashCode()
{
return 1;
}
public override bool Equals(object obj)
{
var rhs = obj as JsonSchema;
if (rhs == null) return false;
return Equals(rhs);
}
public bool Equals(JsonSchema rhs)
{
// skip comparison
if (SkipComparison) return true;
if (rhs.SkipComparison) return true;
return Validator.Equals(rhs.Validator);
}
public static bool operator ==(JsonSchema obj1, JsonSchema obj2)
{
if (ReferenceEquals(obj1, obj2))
{
return true;
}
if (ReferenceEquals(obj1, null))
{
return false;
}
if (ReferenceEquals(obj2, null))
{
return false;
}
return obj1.Equals(obj2);
}
public static bool operator !=(JsonSchema obj1, JsonSchema obj2)
{
return !(obj1 == obj2);
}
#region FromType
public static JsonSchema FromType<T>()
{
return FromType(typeof(T), null, null);
}
public static JsonSchema FromType(Type t,
BaseJsonSchemaAttribute a = null, // field attribute
ItemJsonSchemaAttribute ia = null
)
{
// class attribute
var aa = t.GetCustomAttributes(typeof(JsonSchemaAttribute), true)
.FirstOrDefault() as JsonSchemaAttribute;
if (a != null)
{
a.Merge(aa);
}
else
{
if (aa == null)
{
a = new JsonSchemaAttribute();
}
else
{
a = aa;
}
}
if (ia == null)
{
ia = t.GetCustomAttributes(typeof(ItemJsonSchemaAttribute), true)
.FirstOrDefault() as ItemJsonSchemaAttribute;
}
IJsonSchemaValidator validator = null;
bool skipComparison = a.SkipSchemaComparison;
if (t == typeof(object))
{
skipComparison = true;
}
if (a.EnumValues != null)
{
try
{
validator = JsonEnumValidator.Create(a.EnumValues, a.EnumSerializationType);
}
catch (Exception)
{
throw new Exception(String.Join(", ", a.EnumValues.Select(x => x.ToString()).ToArray()));
}
}
else if (t.IsEnum)
{
validator = JsonEnumValidator.Create(t, a.EnumSerializationType, a.EnumExcludes);
}
else
{
validator = JsonSchemaValidatorFactory.Create(t, a, ia);
}
var schema = new JsonSchema
{
Title = a.Title,
Description = a.Description,
Validator = validator,
SkipComparison = skipComparison,
ExplicitIgnorableValue = a.ExplicitIgnorableValue,
ExplicitIgnorableItemLength = a.ExplicitIgnorableItemLength,
};
return schema;
}
#endregion
#region FromJson
static ValueNodeType ParseValueType(string type)
{
try
{
return (ValueNodeType)Enum.Parse(typeof(ValueNodeType), type, true);
}
catch (ArgumentException)
{
throw new ArgumentException(string.Format("unknown type: {0}", type));
}
}
Stack<string> m_context = new Stack<string>();
static Utf8String s_ref = Utf8String.From("$ref");
public void Parse(IFileSystemAccessor fs, ListTreeNode<JsonValue> root, string Key)
{
m_context.Push(Key);
var compositionType = default(CompositionType);
var composition = new List<JsonSchema>();
foreach (var kv in root.ObjectItems())
{
switch (kv.Key.GetString())
{
case "$schema":
Schema = kv.Value.GetString();
break;
case "$ref":
{
var refFs = fs.Get(kv.Value.GetString());
// parse JSON
var json = refFs.ReadAllText();
var refRoot = JsonParser.Parse(json);
Parse(refFs, refRoot, "$ref");
}
break;
#region Annotation
case "title":
Title = kv.Value.GetString();
break;
case "description":
Description = kv.Value.GetString();
break;
case "default":
Default = kv.Value;
break;
#endregion
#region Validation
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.1
case "type":
if (Validator == null)
{
Validator = JsonSchemaValidatorFactory.Create(kv.Value.GetString());
}
break;
case "enum":
Validator = JsonEnumValidator.Create(kv.Value);
break;
case "const":
break;
#endregion
#region Composite
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.7
case "oneOf":
break;
case "not":
break;
case "anyOf": // composition
case "allOf": // composition
{
compositionType = (CompositionType)Enum.Parse(typeof(CompositionType), kv.Key.GetString(), true);
foreach (var item in kv.Value.ArrayItems())
{
if (item.ContainsKey(s_ref))
{
var sub = JsonSchema.ParseFromPath(fs.Get(item[s_ref].GetString()));
composition.Add(sub);
}
else
{
var sub = new JsonSchema();
sub.Parse(fs, item, compositionType.ToString());
composition.Add(sub);
}
}
Composite(compositionType, composition);
}
break;
#endregion
// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7
case "format":
break;
#region Gltf
case "gltf_detailedDescription":
break;
case "gltf_webgl":
break;
case "gltf_uriType":
break;
#endregion
default:
{
if (Validator != null)
{
if (Validator.FromJsonSchema(fs, kv.Key.GetString(), kv.Value))
{
continue;
}
}
throw new NotImplementedException(string.Format("unknown key: {0}", kv.Key));
}
}
}
m_context.Pop();
if (Validator == null)
{
SkipComparison = true;
}
}
void Composite(CompositionType compositionType, List<JsonSchema> composition)
{
switch (compositionType)
{
case CompositionType.AllOf:
if (composition.Count == 1)
{
// inheritance
if (Validator == null)
{
//Validator = JsonSchemaValidatorFactory.Create(composition[0].Validator.ValueNodeType);
Validator = composition[0].Validator;
}
else
{
Validator.Merge(composition[0].Validator);
}
}
else
{
throw new NotImplementedException();
}
break;
case CompositionType.AnyOf:
if (Validator == null)
{
if (composition.Count == 1)
{
throw new NotImplementedException();
//Validator = composition[0].Validator;
}
else
{
// extend enum
// enum, enum..., type
Validator = JsonEnumValidator.Create(composition, EnumSerializationType.AsString);
}
}
//throw new NotImplementedException();
break;
default:
throw new NotImplementedException();
}
}
public static JsonSchema ParseFromPath(IFileSystemAccessor fs)
{
// parse JSON
var json = fs.ReadAllText();
var root = JsonParser.Parse(json);
// create schema
var schema = new JsonSchema();
schema.Parse(fs, root, "__ParseFromPath__" + fs.ToString());
return schema;
}
#endregion
public void Serialize<T>(IFormatter f, T o, JsonSchemaValidationContext c = null)
{
if (c == null)
{
c = new JsonSchemaValidationContext(o)
{
EnableDiagnosisForNotRequiredFields = true,
};
}
var ex = Validator.Validate(c, o);
if (ex != null)
{
throw ex;
}
Validator.Serialize(f, c, o);
}
public void ToJson(IFormatter f)
{
f.BeginMap(2);
if (!string.IsNullOrEmpty(Title)) { f.Key("title"); f.Value(Title); }
if (!string.IsNullOrEmpty(Description)) { f.Key("description"); f.Value(Description); }
Validator.ToJsonScheama(f);
f.EndMap();
}
public bool IsExplicitlyIgnorableValue<T>(T obj)
{
if (obj == null)
{
return ExplicitIgnorableValue == null;
}
var iter = obj as System.Collections.ICollection;
if (ExplicitIgnorableItemLength != -1 && iter != null)
{
return iter.Count == ExplicitIgnorableItemLength;
}
return obj.Equals(ExplicitIgnorableValue);
}
}
public static class JsonSchemaExtensions
{
public static string Serialize<T>(this JsonSchema s, T o, JsonSchemaValidationContext c = null)
{
var f = new JsonFormatter();
s.Serialize(f, o, c);
return f.ToString();
}
}
}