~ubuntu-branches/ubuntu/trusty/monodevelop/trusty-proposed

« back to all changes in this revision

Viewing changes to external/Newtonsoft.Json/Src/Newtonsoft.Json/Schema/JsonSchemaGenerator.cs

  • Committer: Package Import Robot
  • Author(s): Jo Shields
  • Date: 2013-05-12 09:46:03 UTC
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20130512094603-mad323bzcxvmcam0
Tags: upstream-4.0.5+dfsg
ImportĀ upstreamĀ versionĀ 4.0.5+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#region License
 
2
// Copyright (c) 2007 James Newton-King
 
3
//
 
4
// Permission is hereby granted, free of charge, to any person
 
5
// obtaining a copy of this software and associated documentation
 
6
// files (the "Software"), to deal in the Software without
 
7
// restriction, including without limitation the rights to use,
 
8
// copy, modify, merge, publish, distribute, sublicense, and/or sell
 
9
// copies of the Software, and to permit persons to whom the
 
10
// Software is furnished to do so, subject to the following
 
11
// conditions:
 
12
//
 
13
// The above copyright notice and this permission notice shall be
 
14
// included in all copies or substantial portions of the Software.
 
15
//
 
16
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
17
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 
18
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
19
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 
20
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 
21
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 
22
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 
23
// OTHER DEALINGS IN THE SOFTWARE.
 
24
#endregion
 
25
 
 
26
using System;
 
27
using System.Globalization;
 
28
using System.ComponentModel;
 
29
using System.Collections.Generic;
 
30
using Newtonsoft.Json.Linq;
 
31
using Newtonsoft.Json.Utilities;
 
32
using Newtonsoft.Json.Serialization;
 
33
#if NETFX_CORE
 
34
using IConvertible = Newtonsoft.Json.Utilities.Convertible;
 
35
#endif
 
36
#if NET20
 
37
using Newtonsoft.Json.Utilities.LinqBridge;
 
38
#else
 
39
using System.Linq;
 
40
#endif
 
41
 
 
42
namespace Newtonsoft.Json.Schema
 
43
{
 
44
  /// <summary>
 
45
  /// Generates a <see cref="JsonSchema"/> from a specified <see cref="Type"/>.
 
46
  /// </summary>
 
47
  public class JsonSchemaGenerator
 
48
  {
 
49
    /// <summary>
 
50
    /// Gets or sets how undefined schemas are handled by the serializer.
 
51
    /// </summary>
 
52
    public UndefinedSchemaIdHandling UndefinedSchemaIdHandling { get; set; }
 
53
 
 
54
    private IContractResolver _contractResolver;
 
55
    /// <summary>
 
56
    /// Gets or sets the contract resolver.
 
57
    /// </summary>
 
58
    /// <value>The contract resolver.</value>
 
59
    public IContractResolver ContractResolver
 
60
    {
 
61
      get
 
62
      {
 
63
        if (_contractResolver == null)
 
64
          return DefaultContractResolver.Instance;
 
65
 
 
66
        return _contractResolver;
 
67
      }
 
68
      set { _contractResolver = value; }
 
69
    }
 
70
 
 
71
    private class TypeSchema
 
72
    {
 
73
      public Type Type { get; private set; }
 
74
      public JsonSchema Schema { get; private set;}
 
75
 
 
76
      public TypeSchema(Type type, JsonSchema schema)
 
77
      {
 
78
        ValidationUtils.ArgumentNotNull(type, "type");
 
79
        ValidationUtils.ArgumentNotNull(schema, "schema");
 
80
 
 
81
        Type = type;
 
82
        Schema = schema;
 
83
      }
 
84
    }
 
85
 
 
86
    private JsonSchemaResolver _resolver;
 
87
    private readonly IList<TypeSchema> _stack = new List<TypeSchema>();
 
88
    private JsonSchema _currentSchema;
 
89
 
 
90
    private JsonSchema CurrentSchema
 
91
    {
 
92
      get { return _currentSchema; }
 
93
    }
 
94
 
 
95
    private void Push(TypeSchema typeSchema)
 
96
    {
 
97
      _currentSchema = typeSchema.Schema;
 
98
      _stack.Add(typeSchema);
 
99
      _resolver.LoadedSchemas.Add(typeSchema.Schema);
 
100
    }
 
101
 
 
102
    private TypeSchema Pop()
 
103
    {
 
104
      TypeSchema popped = _stack[_stack.Count - 1];
 
105
      _stack.RemoveAt(_stack.Count - 1);
 
106
      TypeSchema newValue = _stack.LastOrDefault();
 
107
      if (newValue != null)
 
108
      {
 
109
        _currentSchema = newValue.Schema;
 
110
      }
 
111
      else
 
112
      {
 
113
        _currentSchema = null;
 
114
      }
 
115
 
 
116
      return popped;
 
117
    }
 
118
 
 
119
    /// <summary>
 
120
    /// Generate a <see cref="JsonSchema"/> from the specified type.
 
121
    /// </summary>
 
122
    /// <param name="type">The type to generate a <see cref="JsonSchema"/> from.</param>
 
123
    /// <returns>A <see cref="JsonSchema"/> generated from the specified type.</returns>
 
124
    public JsonSchema Generate(Type type)
 
125
    {
 
126
      return Generate(type, new JsonSchemaResolver(), false);
 
127
    }
 
128
 
 
129
    /// <summary>
 
130
    /// Generate a <see cref="JsonSchema"/> from the specified type.
 
131
    /// </summary>
 
132
    /// <param name="type">The type to generate a <see cref="JsonSchema"/> from.</param>
 
133
    /// <param name="resolver">The <see cref="JsonSchemaResolver"/> used to resolve schema references.</param>
 
134
    /// <returns>A <see cref="JsonSchema"/> generated from the specified type.</returns>
 
135
    public JsonSchema Generate(Type type, JsonSchemaResolver resolver)
 
136
    {
 
137
      return Generate(type, resolver, false);
 
138
    }
 
139
 
 
140
    /// <summary>
 
141
    /// Generate a <see cref="JsonSchema"/> from the specified type.
 
142
    /// </summary>
 
143
    /// <param name="type">The type to generate a <see cref="JsonSchema"/> from.</param>
 
144
    /// <param name="rootSchemaNullable">Specify whether the generated root <see cref="JsonSchema"/> will be nullable.</param>
 
145
    /// <returns>A <see cref="JsonSchema"/> generated from the specified type.</returns>
 
146
    public JsonSchema Generate(Type type, bool rootSchemaNullable)
 
147
    {
 
148
      return Generate(type, new JsonSchemaResolver(), rootSchemaNullable);
 
149
    }
 
150
 
 
151
    /// <summary>
 
152
    /// Generate a <see cref="JsonSchema"/> from the specified type.
 
153
    /// </summary>
 
154
    /// <param name="type">The type to generate a <see cref="JsonSchema"/> from.</param>
 
155
    /// <param name="resolver">The <see cref="JsonSchemaResolver"/> used to resolve schema references.</param>
 
156
    /// <param name="rootSchemaNullable">Specify whether the generated root <see cref="JsonSchema"/> will be nullable.</param>
 
157
    /// <returns>A <see cref="JsonSchema"/> generated from the specified type.</returns>
 
158
    public JsonSchema Generate(Type type, JsonSchemaResolver resolver, bool rootSchemaNullable)
 
159
    {
 
160
      ValidationUtils.ArgumentNotNull(type, "type");
 
161
      ValidationUtils.ArgumentNotNull(resolver, "resolver");
 
162
 
 
163
      _resolver = resolver;
 
164
 
 
165
      return GenerateInternal(type, (!rootSchemaNullable) ? Required.Always : Required.Default, false);
 
166
    }
 
167
 
 
168
    private string GetTitle(Type type)
 
169
    {
 
170
      JsonContainerAttribute containerAttribute = JsonTypeReflector.GetJsonContainerAttribute(type);
 
171
 
 
172
      if (containerAttribute != null && !string.IsNullOrEmpty(containerAttribute.Title))
 
173
        return containerAttribute.Title;
 
174
 
 
175
      return null;
 
176
    }
 
177
 
 
178
    private string GetDescription(Type type)
 
179
    {
 
180
      JsonContainerAttribute containerAttribute = JsonTypeReflector.GetJsonContainerAttribute(type);
 
181
 
 
182
      if (containerAttribute != null && !string.IsNullOrEmpty(containerAttribute.Description))
 
183
        return containerAttribute.Description;
 
184
 
 
185
#if !(NETFX_CORE || PORTABLE)
 
186
      DescriptionAttribute descriptionAttribute = ReflectionUtils.GetAttribute<DescriptionAttribute>(type);
 
187
      if (descriptionAttribute != null)
 
188
        return descriptionAttribute.Description;
 
189
#endif
 
190
 
 
191
      return null;
 
192
    }
 
193
 
 
194
    private string GetTypeId(Type type, bool explicitOnly)
 
195
    {
 
196
      JsonContainerAttribute containerAttribute = JsonTypeReflector.GetJsonContainerAttribute(type);
 
197
 
 
198
      if (containerAttribute != null && !string.IsNullOrEmpty(containerAttribute.Id))
 
199
        return containerAttribute.Id;
 
200
 
 
201
      if (explicitOnly)
 
202
        return null;
 
203
 
 
204
      switch (UndefinedSchemaIdHandling)
 
205
      {
 
206
        case UndefinedSchemaIdHandling.UseTypeName:
 
207
          return type.FullName;
 
208
        case UndefinedSchemaIdHandling.UseAssemblyQualifiedName:
 
209
          return type.AssemblyQualifiedName;
 
210
        default:
 
211
          return null;
 
212
      }
 
213
    }
 
214
 
 
215
    private JsonSchema GenerateInternal(Type type, Required valueRequired, bool required)
 
216
    {
 
217
      ValidationUtils.ArgumentNotNull(type, "type");
 
218
 
 
219
      string resolvedId = GetTypeId(type, false);
 
220
      string explicitId = GetTypeId(type, true);
 
221
 
 
222
      if (!string.IsNullOrEmpty(resolvedId))
 
223
      {
 
224
        JsonSchema resolvedSchema = _resolver.GetSchema(resolvedId);
 
225
        if (resolvedSchema != null)
 
226
        {
 
227
          // resolved schema is not null but referencing member allows nulls
 
228
          // change resolved schema to allow nulls. hacky but what are ya gonna do?
 
229
          if (valueRequired != Required.Always && !HasFlag(resolvedSchema.Type, JsonSchemaType.Null))
 
230
            resolvedSchema.Type |= JsonSchemaType.Null;
 
231
          if (required && resolvedSchema.Required != true)
 
232
            resolvedSchema.Required = true;
 
233
 
 
234
          return resolvedSchema;
 
235
        }
 
236
      }
 
237
 
 
238
      // test for unresolved circular reference
 
239
      if (_stack.Any(tc => tc.Type == type))
 
240
      {
 
241
        throw new JsonException("Unresolved circular reference for type '{0}'. Explicitly define an Id for the type using a JsonObject/JsonArray attribute or automatically generate a type Id using the UndefinedSchemaIdHandling property.".FormatWith(CultureInfo.InvariantCulture, type));
 
242
      }
 
243
 
 
244
      JsonContract contract = ContractResolver.ResolveContract(type);
 
245
      JsonConverter converter;
 
246
      if ((converter = contract.Converter) != null || (converter = contract.InternalConverter) != null)
 
247
      {
 
248
        JsonSchema converterSchema = converter.GetSchema();
 
249
        if (converterSchema != null)
 
250
          return converterSchema;
 
251
      }
 
252
 
 
253
      Push(new TypeSchema(type, new JsonSchema()));
 
254
 
 
255
      if (explicitId != null)
 
256
        CurrentSchema.Id = explicitId;
 
257
 
 
258
      if (required)
 
259
        CurrentSchema.Required = true;
 
260
      CurrentSchema.Title = GetTitle(type);
 
261
      CurrentSchema.Description = GetDescription(type);
 
262
 
 
263
      if (converter != null)
 
264
      {
 
265
        // todo: Add GetSchema to JsonConverter and use here?
 
266
        CurrentSchema.Type = JsonSchemaType.Any;
 
267
      }
 
268
      else
 
269
      {
 
270
        switch (contract.ContractType)
 
271
        {
 
272
          case JsonContractType.Object:
 
273
            CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
 
274
            CurrentSchema.Id = GetTypeId(type, false);
 
275
            GenerateObjectSchema(type, (JsonObjectContract) contract);
 
276
            break;
 
277
          case JsonContractType.Array:
 
278
            CurrentSchema.Type = AddNullType(JsonSchemaType.Array, valueRequired);
 
279
 
 
280
            CurrentSchema.Id = GetTypeId(type, false);
 
281
 
 
282
            JsonArrayAttribute arrayAttribute = JsonTypeReflector.GetJsonContainerAttribute(type) as JsonArrayAttribute;
 
283
            bool allowNullItem = (arrayAttribute == null || arrayAttribute.AllowNullItems);
 
284
 
 
285
            Type collectionItemType = ReflectionUtils.GetCollectionItemType(type);
 
286
            if (collectionItemType != null)
 
287
            {
 
288
              CurrentSchema.Items = new List<JsonSchema>();
 
289
              CurrentSchema.Items.Add(GenerateInternal(collectionItemType, (!allowNullItem) ? Required.Always : Required.Default, false));
 
290
            }
 
291
            break;
 
292
          case JsonContractType.Primitive:
 
293
            CurrentSchema.Type = GetJsonSchemaType(type, valueRequired);
 
294
 
 
295
            if (CurrentSchema.Type == JsonSchemaType.Integer && type.IsEnum() && !type.IsDefined(typeof (FlagsAttribute), true))
 
296
            {
 
297
              CurrentSchema.Enum = new List<JToken>();
 
298
              CurrentSchema.Options = new Dictionary<JToken, string>();
 
299
 
 
300
              EnumValues<long> enumValues = EnumUtils.GetNamesAndValues<long>(type);
 
301
              foreach (EnumValue<long> enumValue in enumValues)
 
302
              {
 
303
                JToken value = JToken.FromObject(enumValue.Value);
 
304
 
 
305
                CurrentSchema.Enum.Add(value);
 
306
                CurrentSchema.Options.Add(value, enumValue.Name);
 
307
              }
 
308
            }
 
309
            break;
 
310
          case JsonContractType.String:
 
311
            JsonSchemaType schemaType = (!ReflectionUtils.IsNullable(contract.UnderlyingType))
 
312
                                          ? JsonSchemaType.String
 
313
                                          : AddNullType(JsonSchemaType.String, valueRequired);
 
314
 
 
315
            CurrentSchema.Type = schemaType;
 
316
            break;
 
317
          case JsonContractType.Dictionary:
 
318
            CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
 
319
 
 
320
            Type keyType;
 
321
            Type valueType;
 
322
            ReflectionUtils.GetDictionaryKeyValueTypes(type, out keyType, out valueType);
 
323
 
 
324
            if (keyType != null)
 
325
            {
 
326
              // can be converted to a string
 
327
              if (ConvertUtils.IsConvertible(keyType))
 
328
              {
 
329
                CurrentSchema.AdditionalProperties = GenerateInternal(valueType, Required.Default, false);
 
330
              }
 
331
            }
 
332
            break;
 
333
#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
 
334
          case JsonContractType.Serializable:
 
335
            CurrentSchema.Type = AddNullType(JsonSchemaType.Object, valueRequired);
 
336
            CurrentSchema.Id = GetTypeId(type, false);
 
337
            GenerateISerializableContract(type, (JsonISerializableContract) contract);
 
338
            break;
 
339
#endif
 
340
#if !(NET35 || NET20 || WINDOWS_PHONE || PORTABLE)
 
341
          case JsonContractType.Dynamic:
 
342
#endif
 
343
          case JsonContractType.Linq:
 
344
            CurrentSchema.Type = JsonSchemaType.Any;
 
345
            break;
 
346
          default:
 
347
            throw new JsonException("Unexpected contract type: {0}".FormatWith(CultureInfo.InvariantCulture, contract));
 
348
        }
 
349
      }
 
350
 
 
351
      return Pop().Schema;
 
352
    }
 
353
 
 
354
    private JsonSchemaType AddNullType(JsonSchemaType type, Required valueRequired)
 
355
    {
 
356
      if (valueRequired != Required.Always)
 
357
        return type | JsonSchemaType.Null;
 
358
 
 
359
      return type;
 
360
    }
 
361
 
 
362
    private bool HasFlag(DefaultValueHandling value, DefaultValueHandling flag)
 
363
    {
 
364
      return ((value & flag) == flag);
 
365
    }
 
366
 
 
367
    private void GenerateObjectSchema(Type type, JsonObjectContract contract)
 
368
    {
 
369
      CurrentSchema.Properties = new Dictionary<string, JsonSchema>();
 
370
      foreach (JsonProperty property in contract.Properties)
 
371
      {
 
372
        if (!property.Ignored)
 
373
        {
 
374
          bool optional = property.NullValueHandling == NullValueHandling.Ignore ||
 
375
                          HasFlag(property.DefaultValueHandling.GetValueOrDefault(), DefaultValueHandling.Ignore) ||
 
376
                          property.ShouldSerialize != null ||
 
377
                          property.GetIsSpecified != null;
 
378
 
 
379
          JsonSchema propertySchema = GenerateInternal(property.PropertyType, property.Required, !optional);
 
380
 
 
381
          if (property.DefaultValue != null)
 
382
            propertySchema.Default = JToken.FromObject(property.DefaultValue);
 
383
 
 
384
          CurrentSchema.Properties.Add(property.PropertyName, propertySchema);
 
385
        }
 
386
      }
 
387
 
 
388
      if (type.IsSealed())
 
389
        CurrentSchema.AllowAdditionalProperties = false;
 
390
    }
 
391
 
 
392
#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
 
393
    private void GenerateISerializableContract(Type type, JsonISerializableContract contract)
 
394
    {
 
395
      CurrentSchema.AllowAdditionalProperties = true;
 
396
    }
 
397
#endif
 
398
 
 
399
    internal static bool HasFlag(JsonSchemaType? value, JsonSchemaType flag)
 
400
    {
 
401
      // default value is Any
 
402
      if (value == null)
 
403
        return true;
 
404
 
 
405
      bool match = ((value & flag) == flag);
 
406
      if (match)
 
407
        return true;
 
408
 
 
409
      // integer is a subset of float
 
410
      if (value == JsonSchemaType.Float && flag == JsonSchemaType.Integer)
 
411
        return true;
 
412
 
 
413
      return false;
 
414
    }
 
415
 
 
416
    private JsonSchemaType GetJsonSchemaType(Type type, Required valueRequired)
 
417
    {
 
418
      JsonSchemaType schemaType = JsonSchemaType.None;
 
419
      if (valueRequired != Required.Always && ReflectionUtils.IsNullable(type))
 
420
      {
 
421
        schemaType = JsonSchemaType.Null;
 
422
        if (ReflectionUtils.IsNullableType(type))
 
423
          type = Nullable.GetUnderlyingType(type);
 
424
      }
 
425
 
 
426
      TypeCode typeCode = ConvertUtils.GetTypeCode(type);
 
427
 
 
428
      switch (typeCode)
 
429
      {
 
430
        case TypeCode.Empty:
 
431
        case TypeCode.Object:
 
432
          return schemaType | JsonSchemaType.String;
 
433
#if !(NETFX_CORE || PORTABLE)
 
434
        case TypeCode.DBNull:
 
435
          return schemaType | JsonSchemaType.Null;
 
436
#endif
 
437
        case TypeCode.Boolean:
 
438
          return schemaType | JsonSchemaType.Boolean;
 
439
        case TypeCode.Char:
 
440
          return schemaType | JsonSchemaType.String;
 
441
        case TypeCode.SByte:
 
442
        case TypeCode.Byte:
 
443
        case TypeCode.Int16:
 
444
        case TypeCode.UInt16:
 
445
        case TypeCode.Int32:
 
446
        case TypeCode.UInt32:
 
447
        case TypeCode.Int64:
 
448
        case TypeCode.UInt64:
 
449
          return schemaType | JsonSchemaType.Integer;
 
450
        case TypeCode.Single:
 
451
        case TypeCode.Double:
 
452
        case TypeCode.Decimal:
 
453
          return schemaType | JsonSchemaType.Float;
 
454
        // convert to string?
 
455
        case TypeCode.DateTime:
 
456
          return schemaType | JsonSchemaType.String;
 
457
        case TypeCode.String:
 
458
          return schemaType | JsonSchemaType.String;
 
459
        default:
 
460
          throw new JsonException("Unexpected type code '{0}' for type '{1}'.".FormatWith(CultureInfo.InvariantCulture, typeCode, type));
 
461
      }
 
462
    }
 
463
  }
 
464
}