Json.NET
Code Coverage Statistics for Source File

Newtonsoft.Json\JsonValidatingReader.cs

Symbol Coverage: 89.93% (241 of 268)

Branch Coverage: 93.66% (192 of 205)

Cyclomatic Complexity Avg: 3.28 Max:19

Code Lines: 258


L V Source
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.Collections.Generic;
28
using System.Linq;
29
using System.Text;
30
using Newtonsoft.Json.Linq;
31
using Newtonsoft.Json.Schema;
32
using Newtonsoft.Json.Utilities;
33
using System.Globalization;
34
using System.Text.RegularExpressions;
35

  
36
namespace Newtonsoft.Json
37
{
38
  /// <summary>
39
  /// Represents a reader that provides <see cref="JsonSchema"/> validation.
40
  /// </summary>
41
  public class JsonValidatingReader : JsonReader, IJsonLineInfo
42
  {
43
    private class SchemaScope
44
    {
45
      private readonly JTokenType _tokenType;
46
      private readonly JsonSchemaModel _schema;
47
      private readonly Dictionary<string, bool> _requiredProperties;
48

  
49
      public string CurrentPropertyName { get; set; }
50
      public int ArrayItemCount { get; set; }
51

  
52
      public JsonSchemaModel Schema
53
      {
54
 879
        get { return _schema; }
55
      }
56

  
57
      public Dictionary<string, bool> RequiredProperties
58
      {
59
 204
        get { return _requiredProperties; }
60
      }
61

  
62
      public JTokenType TokenType
63
      {
64
 310
        get { return _tokenType; }
65
      }
66

  
67
 52
      public SchemaScope(JTokenType tokenType, JsonSchemaModel schema)
68
      {
69
 52
        _tokenType = tokenType;
70
 52
        _schema = schema;
71

  
72
 52
        if (_schema != null && _schema.Properties != null)
73
 25
          _requiredProperties = GetRequiredProperties(_schema).Distinct().ToDictionary(p => p, p => false);
74
        else
75
 27
          _requiredProperties = new Dictionary<string, bool>();
76
 52
      }
77

  
78
      private IEnumerable<string> GetRequiredProperties(JsonSchemaModel schema)
79
      {
80
 25
        return schema.Properties.Where(p => !p.Value.Optional).Select(p => p.Key);
81
 25
      }
82
    }
83

  
84
    private readonly JsonReader _reader;
85
    private readonly Stack<SchemaScope> _stack;
86
    private JsonSchema _schema;
87
    private JsonSchemaModel _model;
88
    private SchemaScope _currentScope;
89

  
90
    /// <summary>
91
    /// Sets an event handler for receiving schema validation errors.
92
    /// </summary>
93
    public event ValidationEventHandler ValidationEventHandler;
94

  
95
    /// <summary>
96
    /// Gets the text value of the current Json token.
97
    /// </summary>
98
    /// <value></value>
99
    public override object Value
100
    {
101
 39
      get { return _reader.Value; }
102
    }
103

  
104
    /// <summary>
105
    /// Gets the depth of the current token in the JSON document.
106
    /// </summary>
107
    /// <value>The depth of the current token in the JSON document.</value>
108
    public override int Depth
109
    {
110
 2
      get { return _reader.Depth; }
111
    }
112

  
113
    /// <summary>
114
    /// Gets the quotation mark character used to enclose the value of a string.
115
    /// </summary>
116
    /// <value></value>
117
    public override char QuoteChar
118
    {
119
 3
      get { return _reader.QuoteChar; }
120
 0
      protected internal set { }
121
    }
122

  
123
    /// <summary>
124
    /// Gets the type of the current Json token.
125
    /// </summary>
126
    /// <value></value>
127
    public override JsonToken TokenType
128
    {
129
 179
      get { return _reader.TokenType; }
130
    }
131

  
132
    /// <summary>
133
    /// Gets The Common Language Runtime (CLR) type for the current Json token.
134
    /// </summary>
135
    /// <value></value>
136
    public override Type ValueType
137
    {
138
 1
      get { return _reader.ValueType; }
139
    }
140

  
141
    private void Push(SchemaScope scope)
142
    {
143
 52
      _stack.Push(scope);
144
 52
      _currentScope = scope;
145
 52
    }
146

  
147
    private SchemaScope Pop()
148
    {
149
 51
      SchemaScope poppedScope = _stack.Pop();
150
 51
      _currentScope = (_stack.Count != 0)
151
 51
        ? _stack.Peek()
152
 51
        : null;
153

  
154
 51
      return poppedScope;
155
 51
    }
156

  
157
    private JsonSchemaModel CurrentSchema
158
    {
159
 714
      get { return _currentScope.Schema; }
160
    }
161

  
162
    private JsonSchemaModel CurrentMemberSchema
163
    {
164
      get
165
      {
166
 260
        if (_currentScope == null)
167
 95
          return _model;
168

  
169
 165
        if (_currentScope.Schema == null)
170
 6
          return null;
171

  
172
 159
        switch (_currentScope.TokenType)
173
        {
174
          case JTokenType.None:
175
 0
            return _currentScope.Schema;
176
          case JTokenType.Object:
177
 96
            if (_currentScope.CurrentPropertyName == null)
178
 0
              throw new Exception("CurrentPropertyName has not been set on scope.");
179

  
180
            JsonSchemaModel propertySchema;
181
 96
            if (CurrentSchema.Properties != null && CurrentSchema.Properties.TryGetValue(_currentScope.CurrentPropertyName, out propertySchema))
182
 88
              return propertySchema;
183

  
184
 8
            return (CurrentSchema.AllowAdditionalProperties) ? CurrentSchema.AdditionalProperties : null;
185
          case JTokenType.Array:
186
 63
            if (!CollectionUtils.IsNullOrEmpty(CurrentSchema.Items))
187
            {
188
 52
              if (CurrentSchema.Items.Count == 1)
189
 49
                return CurrentSchema.Items[0];
190

  
191
 3
              if (CurrentSchema.Items.Count > (_currentScope.ArrayItemCount - 1))
192
 2
                return CurrentSchema.Items[_currentScope.ArrayItemCount - 1];
193
            }
194

  
195
 12
            return (CurrentSchema.AllowAdditionalProperties) ? CurrentSchema.AdditionalProperties : null;
196
          case JTokenType.Constructor:
197
 0
            return null;
198
          default:
199
 0
            throw new ArgumentOutOfRangeException("TokenType", "Unexpected token type: {0}".FormatWith(CultureInfo.InvariantCulture, _currentScope.TokenType));
200
        }
201
 260
      }
202
    }
203

  
204
    private void RaiseError(string message, JsonSchemaModel schema)
205
    {
206
 40
      IJsonLineInfo lineInfo = this;
207

  
208
 40
      string exceptionMessage = (lineInfo.HasLineInfo())
209
 40
                                  ? message + " Line {0}, position {1}.".FormatWith(CultureInfo.InvariantCulture, lineInfo.LineNumber, lineInfo.LinePosition)
210
 40
                                  : message;
211

  
212
 40
      OnValidationEvent(new JsonSchemaException(exceptionMessage, null, lineInfo.LineNumber, lineInfo.LinePosition));
213
 38
    }
214

  
215
    private void OnValidationEvent(JsonSchemaException exception)
216
    {
217
 40
      ValidationEventHandler handler = ValidationEventHandler;
218
 40
      if (handler != null)
219
 38
        handler(this, new ValidationEventArgs(exception));
220
      else
221
 2
        throw exception;
222
 38
    }
223

  
224
    /// <summary>
225
    /// Initializes a new instance of the <see cref="JsonValidatingReader"/> class that
226
    /// validates the content returned from the given <see cref="JsonReader"/>.
227
    /// </summary>
228
    /// <param name="reader">The <see cref="JsonReader"/> to read from while validating.</param>
229
 60
    public JsonValidatingReader(JsonReader reader)
230
    {
231
 60
      ValidationUtils.ArgumentNotNull(reader, "reader");
232
 60
      _reader = reader;
233
 60
      _stack = new Stack<SchemaScope>();
234
 60
    }
235

  
236
    /// <summary>
237
    /// Gets or sets the schema.
238
    /// </summary>
239
    /// <value>The schema.</value>
240
    public JsonSchema Schema
241
    {
242
 1
      get { return _schema; }
243
      set
244
      {
245
 59
        if (TokenType != JsonToken.None)
246
 0
          throw new Exception("Cannot change schema while validating JSON.");
247

  
248
 59
        _schema = value;
249
 59
        _model = null;
250
 59
      }
251
    }
252

  
253
    /// <summary>
254
    /// Gets the <see cref="JsonReader"/> used to construct this <see cref="JsonValidatingReader"/>.
255
    /// </summary>
256
    /// <value>The <see cref="JsonReader"/> specified in the constructor.</value>
257
    public JsonReader Reader
258
    {
259
 1
      get { return _reader; }
260
    }
261

  
262
    private void ValidateInEnumAndNotDisallowed(JsonSchemaModel schema)
263
    {
264
 137
      if (schema == null)
265
 0
        return;
266

  
267
 137
      JToken value = new JValue(_reader.Value);
268

  
269
 137
      if (schema.Enum != null)
270
      {
271
 13
        if (!schema.Enum.ContainsValue(value, new JTokenEqualityComparer()))
272
 5
          RaiseError("Value {0} is not defined in enum.".FormatWith(CultureInfo.InvariantCulture, value),
273
 5
                     schema);
274
      }
275

  
276
 137
      JsonSchemaType? currentNodeType = GetCurrentNodeSchemaType();
277
 137
      if (currentNodeType != null)
278
      {
279
 137
        if (JsonSchemaGenerator.HasFlag(schema.Disallow, currentNodeType.Value))
280
 1
          RaiseError("Type {0} is disallowed.".FormatWith(CultureInfo.InvariantCulture, currentNodeType), schema);
281
      }
282
 137
    }
283

  
284
    private JsonSchemaType? GetCurrentNodeSchemaType()
285
    {
286
 137
      switch (_reader.TokenType)
287
      {
288
        case JsonToken.StartObject:
289
 0
          return JsonSchemaType.Object;
290
        case JsonToken.StartArray:
291
 0
          return JsonSchemaType.Array;
292
        case JsonToken.Integer:
293
 29
          return JsonSchemaType.Integer;
294
        case JsonToken.Float:
295
 13
          return JsonSchemaType.Float;
296
        case JsonToken.String:
297
 65
          return JsonSchemaType.String;
298
        case JsonToken.Boolean:
299
 5
          return JsonSchemaType.Boolean;
300
        case JsonToken.Null:
301
 25
          return JsonSchemaType.Null;
302
        default:
303
 0
          return null;
304
      }
305
 137
    }
306

  
307
    /// <summary>
308
    /// Reads the next JSON token from the stream as a <see cref="T:Byte[]"/>.
309
    /// </summary>
310
    /// <returns>
311
    /// A <see cref="T:Byte[]"/> or a null reference if the next JSON token is null.
312
    /// </returns>
313
    public override byte[] ReadAsBytes()
314
    {
315
 0
      byte[] data = _reader.ReadAsBytes();
316

  
317
 0
      ValidateCurrentToken();
318
 0
      return data;
319
 0
    }
320

  
321
    /// <summary>
322
    /// Reads the next JSON token from the stream.
323
    /// </summary>
324
    /// <returns>
325
    /// true if the next token was read successfully; false if there are no more tokens to read.
326
    /// </returns>
327
    public override bool Read()
328
    {
329
 393
      if (!_reader.Read())
330
 33
        return false;
331

  
332
 360
      if (_reader.TokenType == JsonToken.Comment)
333
 0
        return true;
334

  
335
 360
      ValidateCurrentToken();
336
 358
      return true;
337
 391
    }
338

  
339
    private void ValidateCurrentToken()
340
    {
341
      // first time validate has been called. build model
342
 360
      if (_model == null)
343
      {
344
 59
        JsonSchemaModelBuilder builder = new JsonSchemaModelBuilder();
345
 59
        _model = builder.Build(_schema);
346
      }
347

  
348
 360
      switch (_reader.TokenType)
349
      {
350
        case JsonToken.StartObject:
351
 29
          ProcessValue();
352
 29
          JsonSchemaModel objectSchema = (ValidateObject(CurrentMemberSchema))
353
 29
                                           ? CurrentMemberSchema 
354
 29
                                           : null;
355
 29
          Push(new SchemaScope(JTokenType.Object, objectSchema));
356
 29
          break;
357
        case JsonToken.StartArray:
358
 23
          ProcessValue();
359
 23
          JsonSchemaModel arraySchema = (ValidateArray(CurrentMemberSchema))
360
 23
                                          ? CurrentMemberSchema
361
 23
                                          : null;
362
 23
          Push(new SchemaScope(JTokenType.Array, arraySchema));
363
 23
          break;
364
        case JsonToken.StartConstructor:
365
 0
          Push(new SchemaScope(JTokenType.Constructor, null));
366
 0
          break;
367
        case JsonToken.PropertyName:
368
 94
          ValidatePropertyName(CurrentSchema);
369
 94
          break;
370
        case JsonToken.Raw:
371
 0
          break;
372
        case JsonToken.Integer:
373
 31
          ProcessValue();
374
 31
          ValidateInteger(CurrentMemberSchema);
375
 30
          break;
376
        case JsonToken.Float:
377
 14
          ProcessValue();
378
 14
          ValidateFloat(CurrentMemberSchema);
379
 14
          break;
380
        case JsonToken.String:
381
 73
          ProcessValue();
382
 73
          ValidateString(CurrentMemberSchema);
383
 72
          break;
384
        case JsonToken.Boolean:
385
 5
          ProcessValue();
386
 5
          ValidateBoolean(CurrentMemberSchema);
387
 5
          break;
388
        case JsonToken.Null:
389
 34
          ProcessValue();
390
 34
          ValidateNull(CurrentMemberSchema);
391
 34
          break;
392
        case JsonToken.Undefined:
393
 0
          break;
394
        case JsonToken.EndObject:
395
 29
          ValidateEndObject(CurrentSchema);
396
 29
          Pop();
397
 29
          break;
398
        case JsonToken.EndArray:
399
 22
          ValidateEndArray(CurrentSchema);
400
 22
          Pop();
401
 22
          break;
402
        case JsonToken.EndConstructor:
403
 0
          Pop();
404
 0
          break;
405
        case JsonToken.Date:
406
 6
          break;
407
        default:
408
 0
          throw new ArgumentOutOfRangeException();
409
      }
410
 358
    }
411

  
412
    private void ValidateEndObject(JsonSchemaModel schema)
413
    {
414
 29
      if (schema == null)
415
 1
        return;
416

  
417
 28
      Dictionary<string, bool> requiredProperties = _currentScope.RequiredProperties;
418

  
419
 28
      if (requiredProperties != null)
420
      {
421
 28
        List<string> unmatchedRequiredProperties =
422
 6
          requiredProperties.Where(kv => !kv.Value).Select(kv => kv.Key).ToList();
423

  
424
 28
        if (unmatchedRequiredProperties.Count > 0)
425
 4
          RaiseError("Non-optional properties are missing from object: {0}.".FormatWith(CultureInfo.InvariantCulture, string.Join(", ", unmatchedRequiredProperties.ToArray())), schema);
426
      }
427
 29
    }
428

  
429
    private void ValidateEndArray(JsonSchemaModel schema)
430
    {
431
 22
      if (schema == null)
432
 2
        return;
433

  
434
 20
      int arrayItemCount = _currentScope.ArrayItemCount;
435

  
436
 20
      if (schema.MaximumItems != null && arrayItemCount > schema.MaximumItems)
437
 1
        RaiseError("Array item count {0} exceeds maximum count of {1}.".FormatWith(CultureInfo.InvariantCulture, arrayItemCount, schema.MaximumItems), schema);
438

  
439
 20
      if (schema.MinimumItems != null && arrayItemCount < schema.MinimumItems)
440
 1
        RaiseError("Array item count {0} is less than minimum count of {1}.".FormatWith(CultureInfo.InvariantCulture, arrayItemCount, schema.MinimumItems), schema);
441
 22
    }
442

  
443
    private void ValidateNull(JsonSchemaModel schema)
444
    {
445
 34
      if (schema == null)
446
 9
        return;
447

  
448
 25
      if (!TestType(schema, JsonSchemaType.Null))
449
 0
        return;
450

  
451
 25
      ValidateInEnumAndNotDisallowed(schema);
452
 34
    }
453

  
454
    private void ValidateBoolean(JsonSchemaModel schema)
455
    {
456
 5
      if (schema == null)
457
 0
        return;
458

  
459
 5
      if (!TestType(schema, JsonSchemaType.Boolean))
460
 0
        return;
461

  
462
 5
      ValidateInEnumAndNotDisallowed(schema);
463
 5
    }
464

  
465
    private void ValidateString(JsonSchemaModel schema)
466
    {
467
 73
      if (schema == null)
468
 6
        return;
469

  
470
 67
      if (!TestType(schema, JsonSchemaType.String))
471
 2
        return;
472

  
473
 65
      ValidateInEnumAndNotDisallowed(schema);
474

  
475
 65
      string value = _reader.Value.ToString();
476

  
477
 65
      if (schema.MaximumLength != null && value.Length > schema.MaximumLength)
478
 3
        RaiseError("String '{0}' exceeds maximum length of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.MaximumLength), schema);
479

  
480
 65
      if (schema.MinimumLength != null && value.Length < schema.MinimumLength)
481
 3
        RaiseError("String '{0}' is less than minimum length of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.MinimumLength), schema);
482

  
483
 65
      if (schema.Patterns != null)
484
      {
485
 6
        foreach (string pattern in schema.Patterns)
486
        {
487
 7
          if (!Regex.IsMatch(value, pattern))
488
 5
            RaiseError("String '{0}' does not match regex pattern '{1}'.".FormatWith(CultureInfo.InvariantCulture, value, pattern), schema);
489
        }
490
      }
491
 72
    }
492

  
493
    private void ValidateInteger(JsonSchemaModel schema)
494
    {
495
 31
      if (schema == null)
496
 0
        return;
497

  
498
 31
      if (!TestType(schema, JsonSchemaType.Integer))
499
 2
        return;
500

  
501
 29
      ValidateInEnumAndNotDisallowed(schema);
502
      
503
 29
      long value = Convert.ToInt64(_reader.Value, CultureInfo.InvariantCulture);
504

  
505
 29
      if (schema.Maximum != null && value > schema.Maximum)
506
 2
        RaiseError("Integer {0} exceeds maximum value of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.Maximum), schema);
507

  
508
 28
      if (schema.Minimum != null && value < schema.Minimum)
509
 1
        RaiseError("Integer {0} is less than minimum value of {1}.".FormatWith(CultureInfo.InvariantCulture, value, schema.Minimum), schema);
510
 30
    }
511

  
512
    private void ProcessValue()
513
    {
514
 209
      if (_currentScope != null && _currentScope.TokenType == JTokenType.Array)
515
      {
516
 62
        _currentScope.ArrayItemCount++;
517

  
518
 62
        if (CurrentSchema != null && CurrentSchema.Items != null && CurrentSchema.Items.Count > 1 && _currentScope.ArrayItemCount >= CurrentSchema.Items.Count)
519
 2
          RaiseError("Index {0} has not been defined and the schema does not allow additional items.".FormatWith(CultureInfo.InvariantCulture, _currentScope.ArrayItemCount), CurrentSchema);
520
      }
521
 209
    }
522

  
523
    private void ValidateFloat(JsonSchemaModel schema)
524
    {
525
 14
      if (schema == null)
526
 1
        return;
527

  
528
 13
      if (!TestType(schema, JsonSchemaType.Float))
529
 0
        return;
530

  
531
 13
      ValidateInEnumAndNotDisallowed(schema);
532
      
533
 13
      double value = Convert.ToDouble(_reader.Value, CultureInfo.InvariantCulture);
534

  
535
 13
      if (schema.Maximum != null && value > schema.Maximum)
536
 1
        RaiseError("Float {0} exceeds maximum value of {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Maximum), schema);
537

  
538
 13
      if (schema.Minimum != null && value < schema.Minimum)
539
 1
        RaiseError("Float {0} is less than minimum value of {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.Minimum), schema);
540

  
541
 13
      if (schema.MaximumDecimals != null && MathUtils.GetDecimalPlaces(value) > schema.MaximumDecimals)
542
 1
        RaiseError("Float {0} exceeds the maximum allowed number decimal places of {1}.".FormatWith(CultureInfo.InvariantCulture, JsonConvert.ToString(value), schema.MaximumDecimals), schema);
543
 14
    }
544

  
545
    private void ValidatePropertyName(JsonSchemaModel schema)
546
    {
547
 94
      if (schema == null)
548
 2
        return;
549

  
550
 92
      string propertyName = Convert.ToString(_reader.Value, CultureInfo.InvariantCulture);
551

  
552
 92
      if (_currentScope.RequiredProperties.ContainsKey(propertyName))
553
 84
        _currentScope.RequiredProperties[propertyName] = true;
554

  
555
 92
      if (schema.Properties != null && !schema.Properties.ContainsKey(propertyName))
556
      {
557
 5
        IList<string> definedProperties = schema.Properties.Select(p => p.Key).ToList();
558

  
559
 5
        if (!schema.AllowAdditionalProperties && !definedProperties.Contains(propertyName))
560
        {
561
 4
          RaiseError("Property '{0}' has not been defined and the schema does not allow additional properties.".FormatWith(CultureInfo.InvariantCulture, propertyName), schema);
562
        }
563
      }
564

  
565
 92
      _currentScope.CurrentPropertyName = propertyName;
566
 94
    }
567

  
568
    private bool ValidateArray(JsonSchemaModel schema)
569
    {
570
 23
      if (schema == null)
571
 2
        return true;
572

  
573
 21
      return (TestType(schema, JsonSchemaType.Array));
574
 23
    }
575

  
576
    private bool ValidateObject(JsonSchemaModel schema)
577
    {
578
 29
      if (schema == null)
579
 1
        return true;
580

  
581
 28
      return (TestType(schema, JsonSchemaType.Object));
582
 29
    }
583

  
584
    private bool TestType(JsonSchemaModel currentSchema, JsonSchemaType currentType)
585
    {
586
 190
      if (!JsonSchemaGenerator.HasFlag(currentSchema.Type, currentType))
587
      {
588
 5
        RaiseError("Invalid type. Expected {0} but got {1}.".FormatWith(CultureInfo.InvariantCulture, currentSchema.Type, currentType), currentSchema);
589
 5
        return false;
590
      }
591

  
592
 185
      return true;
593
 190
    }
594

  
595
    bool IJsonLineInfo.HasLineInfo()
596
    {
597
 40
      IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
598
 40
      return (lineInfo != null) ? lineInfo.HasLineInfo() : false;
599
 40
    }
600

  
601
    int IJsonLineInfo.LineNumber
602
    {
603
      get
604
      {
605
 76
        IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
606
 76
        return (lineInfo != null) ? lineInfo.LineNumber : 0;
607
 76
      }
608
    }
609

  
610
    int IJsonLineInfo.LinePosition
611
    {
612
      get
613
      {
614
 76
        IJsonLineInfo lineInfo = _reader as IJsonLineInfo;
615
 76
        return (lineInfo != null) ? lineInfo.LinePosition : 0;
616
 76
      }
617
    }
618
  }
619
}