#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using Newtonsoft.Json.Utilities;
using Newtonsoft.Json.Linq;
namespace Newtonsoft.Json.Schema{
internal class JsonSchemaBuilder
{
private JsonReader _reader;
private readonly IList<JsonSchema> _stack;
private readonly JsonSchemaResolver _resolver;
private JsonSchema _currentSchema;
private void Push(JsonSchema value)
{
_currentSchema = value;
_stack.Add(value);
_resolver.LoadedSchemas.Add(value);
}
private JsonSchema Pop()
{
JsonSchema poppedSchema = _currentSchema;
_stack.RemoveAt(_stack.Count - 1);
_currentSchema = _stack.LastOrDefault();
return poppedSchema;
}
private JsonSchema CurrentSchema
{
get { return _currentSchema; }
}
public JsonSchemaBuilder(JsonSchemaResolver resolver)
{
_stack = new List<JsonSchema>();
_resolver = resolver;
}
internal JsonSchema Parse(JsonReader reader)
{
_reader = reader;
if (reader.TokenType == JsonToken.None)
_reader.Read();
return BuildSchema();
}
private JsonSchema BuildSchema()
{
if (_reader.TokenType != JsonToken.StartObject)
throw new Exception("Expected StartObject while parsing schema object, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
_reader.Read();
// empty schema object
if (_reader.TokenType == JsonToken.EndObject)
{
Push(new JsonSchema());
return Pop();
}
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
_reader.Read();
// schema reference
if (propertyName == JsonSchemaConstants.ReferencePropertyName)
{
string id = (string)_reader.Value;
_reader.Read();
JsonSchema referencedSchema = _resolver.GetSchema(id);
if (referencedSchema == null)
throw new Exception("Could not resolve schema reference for Id '{0}'.".FormatWith(CultureInfo.InvariantCulture, id));
return referencedSchema;
}
// regular ol' schema object
Push(new JsonSchema());
ProcessSchemaProperty(propertyName);
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
{
propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
_reader.Read();
ProcessSchemaProperty(propertyName);
}
return Pop();
}
private void ProcessSchemaProperty(string propertyName)
{
switch (propertyName)
{
case JsonSchemaConstants.TypePropertyName:
CurrentSchema.Type = ProcessType();
break;
case JsonSchemaConstants.IdPropertyName:
CurrentSchema.Id = (string) _reader.Value;
break;
case JsonSchemaConstants.TitlePropertyName:
CurrentSchema.Title = (string) _reader.Value;
break;
case JsonSchemaConstants.DescriptionPropertyName:
CurrentSchema.Description = (string)_reader.Value;
break;
case JsonSchemaConstants.PropertiesPropertyName:
ProcessProperties();
break;
case JsonSchemaConstants.ItemsPropertyName:
ProcessItems();
break;
case JsonSchemaConstants.AdditionalPropertiesPropertyName:
ProcessAdditionalProperties();
break;
case JsonSchemaConstants.OptionalPropertyName:
CurrentSchema.Optional = (bool)_reader.Value;
break;
case JsonSchemaConstants.RequiresPropertyName:
CurrentSchema.Requires = (string) _reader.Value;
break;
case JsonSchemaConstants.IdentityPropertyName:
ProcessIdentity();
break;
case JsonSchemaConstants.MinimumPropertyName:
CurrentSchema.Minimum = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MaximumPropertyName:
CurrentSchema.Maximum = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MaximumLengthPropertyName:
CurrentSchema.MaximumLength = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MinimumLengthPropertyName:
CurrentSchema.MinimumLength = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MaximumItemsPropertyName:
CurrentSchema.MaximumItems = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MinimumItemsPropertyName:
CurrentSchema.MinimumItems = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.MaximumDecimalsPropertyName:
CurrentSchema.MaximumDecimals = Convert.ToInt32(_reader.Value, CultureInfo.InvariantCulture);
break;
case JsonSchemaConstants.DisallowPropertyName:
CurrentSchema.Disallow = ProcessType();
break;
case JsonSchemaConstants.DefaultPropertyName:
ProcessDefault();
break;
case JsonSchemaConstants.HiddenPropertyName:
CurrentSchema.Hidden = (bool) _reader.Value;
break;
case JsonSchemaConstants.ReadOnlyPropertyName:
CurrentSchema.ReadOnly = (bool) _reader.Value;
break;
case JsonSchemaConstants.FormatPropertyName:
CurrentSchema.Format = (string) _reader.Value;
break;
case JsonSchemaConstants.PatternPropertyName:
CurrentSchema.Pattern = (string) _reader.Value;
break;
case JsonSchemaConstants.OptionsPropertyName:
ProcessOptions();
break;
case JsonSchemaConstants.EnumPropertyName:
ProcessEnum();
break;
case JsonSchemaConstants.ExtendsPropertyName:
ProcessExtends();
break;
default:
_reader.Skip();
break;
}
}
private void ProcessExtends()
{
CurrentSchema.Extends = BuildSchema();
}
private void ProcessEnum()
{
if (_reader.TokenType != JsonToken.StartArray)
throw new Exception("Expected StartArray token while parsing enum values, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
CurrentSchema.Enum = new List<JToken>();
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
{
JToken value = JToken.ReadFrom(_reader);
CurrentSchema.Enum.Add(value);
}
}
private void ProcessOptions()
{
CurrentSchema.Options = new Dictionary<JToken, string>(new JTokenEqualityComparer());
switch (_reader.TokenType)
{
case JsonToken.StartArray:
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
{
if (_reader.TokenType != JsonToken.StartObject)
throw new Exception("Expect object token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
string label = null;
JToken value = null;
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
{
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
_reader.Read();
switch (propertyName)
{
case JsonSchemaConstants.OptionValuePropertyName:
value = JToken.ReadFrom(_reader);
break;
case JsonSchemaConstants.OptionLabelPropertyName:
label = (string) _reader.Value;
break;
default:
throw new Exception("Unexpected property in JSON schema option: {0}.".FormatWith(CultureInfo.InvariantCulture, propertyName));
}
}
if (value == null)
throw new Exception("No value specified for JSON schema option.");
if (CurrentSchema.Options.ContainsKey(value))
throw new Exception("Duplicate value in JSON schema option collection: {0}".FormatWith(CultureInfo.InvariantCulture, value));
CurrentSchema.Options.Add(value, label);
}
break;
default:
throw new Exception("Expected array token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
}
}
private void ProcessDefault()
{
CurrentSchema.Default = JToken.ReadFrom(_reader);
}
private void ProcessIdentity()
{
CurrentSchema.Identity = new List<string>();
switch (_reader.TokenType)
{
case JsonToken.String:
CurrentSchema.Identity.Add(_reader.Value.ToString());
break;
case JsonToken.StartArray:
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
{
if (_reader.TokenType != JsonToken.String)
throw new Exception("Exception JSON property name string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
CurrentSchema.Identity.Add(_reader.Value.ToString());
}
break;
default:
throw new Exception("Expected array or JSON property name string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
}
}
private void ProcessAdditionalProperties()
{
if (_reader.TokenType == JsonToken.Boolean)
CurrentSchema.AllowAdditionalProperties = (bool)_reader.Value;
else
CurrentSchema.AdditionalProperties = BuildSchema();
}
private void ProcessItems()
{
CurrentSchema.Items = new List<JsonSchema>();
switch (_reader.TokenType)
{
case JsonToken.StartObject:
CurrentSchema.Items.Add(BuildSchema());
break;
case JsonToken.StartArray:
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
{
CurrentSchema.Items.Add(BuildSchema());
}
break;
default:
throw new Exception("Expected array or JSON schema object token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
}
}
private void ProcessProperties()
{
IDictionary<string, JsonSchema> properties = new Dictionary<string, JsonSchema>();
if (_reader.TokenType != JsonToken.StartObject)
throw new Exception("Expected StartObject token while parsing schema properties, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
while (_reader.Read() && _reader.TokenType != JsonToken.EndObject)
{
string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
_reader.Read();
if (properties.ContainsKey(propertyName))
throw new Exception("Property {0} has already been defined in schema.".FormatWith(CultureInfo.InvariantCulture, propertyName));
properties.Add(propertyName, BuildSchema());
}
CurrentSchema.Properties = properties;
}
private JsonSchemaType? ProcessType()
{
switch (_reader.TokenType)
{
case JsonToken.String:
return MapType(_reader.Value.ToString());
case JsonToken.StartArray:
// ensure type is in blank state before ORing values
JsonSchemaType? type = JsonSchemaType.None;
while (_reader.Read() && _reader.TokenType != JsonToken.EndArray)
{
if (_reader.TokenType != JsonToken.String)
throw new Exception("Exception JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
type = type | MapType(_reader.Value.ToString());
}
return type;
default:
throw new Exception("Expected array or JSON schema type string token, got {0}.".FormatWith(CultureInfo.InvariantCulture, _reader.TokenType));
}
}
internal static JsonSchemaType MapType(string type)
{
JsonSchemaType mappedType;
if (!JsonSchemaConstants.JsonSchemaTypeMapping.TryGetValue(type, out mappedType))
throw new Exception("Invalid JSON schema type: {0}".FormatWith(CultureInfo.InvariantCulture, type));
return mappedType;
}
internal static string MapType(JsonSchemaType type)
{
return JsonSchemaConstants.JsonSchemaTypeMapping.Single(kv => kv.Value == type).Key;
}
}
}
|