Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit 0517eee

Browse files
author
Victor Hurdugaci
committed
- Support for JSON arrays
- Use the json.net parser instead of manually parsing the json - Simplified some test code
1 parent 7d42e66 commit 0517eee

File tree

24 files changed

+770
-331
lines changed

24 files changed

+770
-331
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using Newtonsoft.Json;
9+
using Newtonsoft.Json.Linq;
10+
11+
namespace Microsoft.Framework.ConfigurationModel.Json
12+
{
13+
internal class JsonConfigurationFileParser
14+
{
15+
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
16+
private readonly Stack<string> _context = new Stack<string>();
17+
private string _currentPath;
18+
19+
private JsonTextReader _reader;
20+
21+
public IDictionary<string, string> Parse(Stream input)
22+
{
23+
_data.Clear();
24+
_reader = new JsonTextReader(new StreamReader(input));
25+
_reader.DateParseHandling = DateParseHandling.None;
26+
27+
var jsonConfig = JObject.Load(_reader);
28+
29+
VisitJObject(jsonConfig);
30+
31+
return _data;
32+
}
33+
34+
private void VisitJObject(JObject jObject)
35+
{
36+
foreach (var property in jObject.Properties())
37+
{
38+
EnterContext(property.Name);
39+
VisitProperty(property);
40+
ExitContext();
41+
}
42+
}
43+
44+
private void VisitProperty(JProperty property)
45+
{
46+
VisitToken(property.Value);
47+
}
48+
49+
private void VisitToken(JToken token)
50+
{
51+
switch (token.Type)
52+
{
53+
case JTokenType.Object:
54+
VisitJObject(token.Value<JObject>());
55+
break;
56+
57+
case JTokenType.Array:
58+
VisitArray(token.Value<JArray>());
59+
break;
60+
61+
case JTokenType.Integer:
62+
case JTokenType.Float:
63+
case JTokenType.String:
64+
case JTokenType.Boolean:
65+
case JTokenType.Bytes:
66+
case JTokenType.Raw:
67+
case JTokenType.Null:
68+
VisitPrimitive(token);
69+
break;
70+
71+
default:
72+
throw new FormatException(Resources.FormatError_UnsupportedJSONToken(
73+
_reader.TokenType,
74+
_reader.Path,
75+
_reader.LineNumber,
76+
_reader.LinePosition));
77+
}
78+
}
79+
80+
private void VisitArray(JArray array)
81+
{
82+
for (int index = 0; index < array.Count; index++)
83+
{
84+
EnterContext(index.ToString());
85+
VisitToken(array[index]);
86+
ExitContext();
87+
}
88+
}
89+
90+
private void VisitPrimitive(JToken data)
91+
{
92+
var key = _currentPath;
93+
94+
if (_data.ContainsKey(key))
95+
{
96+
throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
97+
}
98+
_data[key] = data.ToString();
99+
}
100+
101+
private void EnterContext(string context)
102+
{
103+
_context.Push(context);
104+
_currentPath = string.Join(":", _context.Reverse());
105+
}
106+
107+
private void ExitContext()
108+
{
109+
_context.Pop();
110+
_currentPath = string.Join(":", _context.Reverse());
111+
}
112+
}
113+
}

src/Microsoft.Framework.ConfigurationModel.Json/JsonConfigurationSource.cs

Lines changed: 2 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using Microsoft.Framework.ConfigurationModel.Json;
8-
using Newtonsoft.Json;
98

109
namespace Microsoft.Framework.ConfigurationModel
1110
{
@@ -78,129 +77,8 @@ public override void Load()
7877

7978
internal void Load(Stream stream)
8079
{
81-
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
82-
83-
using (var reader = new JsonTextReader(new StreamReader(stream)))
84-
{
85-
var startObjectCount = 0;
86-
87-
// Dates are parsed as strings
88-
reader.DateParseHandling = DateParseHandling.None;
89-
90-
// Move to the first token
91-
reader.Read();
92-
93-
SkipComments(reader);
94-
95-
if (reader.TokenType != JsonToken.StartObject)
96-
{
97-
throw new FormatException(Resources.FormatError_RootMustBeAnObject(reader.Path,
98-
reader.LineNumber, reader.LinePosition));
99-
}
100-
101-
do
102-
{
103-
SkipComments(reader);
104-
105-
switch (reader.TokenType)
106-
{
107-
case JsonToken.StartObject:
108-
startObjectCount++;
109-
break;
110-
111-
case JsonToken.EndObject:
112-
startObjectCount--;
113-
break;
114-
115-
// Keys in key-value pairs
116-
case JsonToken.PropertyName:
117-
break;
118-
119-
// Values in key-value pairs
120-
case JsonToken.Integer:
121-
case JsonToken.Float:
122-
case JsonToken.String:
123-
case JsonToken.Boolean:
124-
case JsonToken.Bytes:
125-
case JsonToken.Raw:
126-
case JsonToken.Null:
127-
var key = GetKey(reader.Path);
128-
129-
if (data.ContainsKey(key))
130-
{
131-
throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
132-
}
133-
data[key] = reader.Value.ToString();
134-
break;
135-
136-
// End of file
137-
case JsonToken.None:
138-
{
139-
throw new FormatException(Resources.FormatError_UnexpectedEnd(reader.Path,
140-
reader.LineNumber, reader.LinePosition));
141-
}
142-
143-
default:
144-
{
145-
// Unsupported elements: Array, Constructor, Undefined
146-
throw new FormatException(Resources.FormatError_UnsupportedJSONToken(
147-
reader.TokenType, reader.Path, reader.LineNumber, reader.LinePosition));
148-
}
149-
}
150-
151-
reader.Read();
152-
153-
} while (startObjectCount > 0);
154-
}
155-
156-
Data = data;
157-
}
158-
159-
private string GetKey(string jsonPath)
160-
{
161-
var pathSegments = new List<string>();
162-
var index = 0;
163-
164-
while (index < jsonPath.Length)
165-
{
166-
// If the JSON element contains '.' in its name, JSON.net escapes that element as ['element']
167-
// while getting its Path. So before replacing '.' => ':' to represent JSON hierarchy, here
168-
// we skip a '.' => ':' conversion if the element is not enclosed with in ['..'].
169-
var start = jsonPath.IndexOf("['", index);
170-
171-
if (start < 0)
172-
{
173-
// No more ['. Skip till end of string.
174-
pathSegments.Add(jsonPath.
175-
Substring(index).
176-
Replace('.', ':'));
177-
break;
178-
}
179-
else
180-
{
181-
if (start > index)
182-
{
183-
pathSegments.Add(
184-
jsonPath
185-
.Substring(index, start - index) // Anything between the previous [' and '].
186-
.Replace('.', ':'));
187-
}
188-
189-
var endIndex = jsonPath.IndexOf("']", start);
190-
pathSegments.Add(jsonPath.Substring(start + 2, endIndex - start - 2));
191-
index = endIndex + 2;
192-
}
193-
}
194-
195-
return string.Join(string.Empty, pathSegments);
196-
}
197-
198-
private void SkipComments(JsonReader reader)
199-
{
200-
while (reader.TokenType == JsonToken.Comment)
201-
{
202-
reader.Read();
203-
}
80+
JsonConfigurationFileParser parser = new JsonConfigurationFileParser();
81+
Data = parser.Parse(stream);
20482
}
20583
}
20684
}

src/Microsoft.Framework.ConfigurationModel.Json/Properties/Resources.Designer.cs

Lines changed: 0 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Framework.ConfigurationModel.Json/Resources.resx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,6 @@
132132
<data name="Error_KeyIsDuplicated" xml:space="preserve">
133133
<value>A duplicate key '{0}' was found.</value>
134134
</data>
135-
<data name="Error_RootMustBeAnObject" xml:space="preserve">
136-
<value>Only an object can be the root. Path '{0}', line {1} position {2}.</value>
137-
</data>
138-
<data name="Error_UnexpectedEnd" xml:space="preserve">
139-
<value>Unexpected end when parsing JSON. Path '{0}', line {1} position {2}.</value>
140-
</data>
141135
<data name="Error_UnsupportedJSONToken" xml:space="preserve">
142136
<value>Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}.</value>
143137
</data>

src/Microsoft.Framework.ConfigurationModel.Json/project.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"frameworks": {
1010
"net45": { },
1111
"dnx451": { },
12-
"dnxcore50": { }
12+
"dnxcore50": {
13+
"dependencies": {
14+
"System.Dynamic.Runtime": "4.0.10-*"
15+
}
16+
}
1317
}
1418
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
7+
namespace Microsoft.Framework.ConfigurationModel
8+
{
9+
public class ConfigurationKeyComparer : IComparer<string>
10+
{
11+
private const char Separator = ':';
12+
13+
public static ConfigurationKeyComparer Instance { get; } = new ConfigurationKeyComparer();
14+
15+
public int Compare(string x, string y)
16+
{
17+
var xParts = x?.Split(Separator) ?? new string[0];
18+
var yParts = y?.Split(Separator) ?? new string[0];
19+
20+
// Compare each part until we get two parts that are not equal
21+
for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++)
22+
{
23+
x = xParts[i];
24+
y = yParts[i];
25+
26+
var value1 = 0;
27+
var value2 = 0;
28+
29+
var xIsInt = x != null && int.TryParse(x, out value1);
30+
var yIsInt = y != null && int.TryParse(y, out value2);
31+
32+
int result = 0;
33+
34+
if (!xIsInt && !yIsInt)
35+
{
36+
// Both are strings
37+
result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
38+
}
39+
else if (xIsInt && yIsInt)
40+
{
41+
// Both are int
42+
result = value1 - value2;
43+
}
44+
else
45+
{
46+
// Only one of them is int
47+
result = xIsInt ? -1 : 1;
48+
}
49+
50+
if (result != 0)
51+
{
52+
// One of them is different
53+
return result;
54+
}
55+
}
56+
57+
// If we get here, the common parts are equal.
58+
// If they are of the same length, then they are totally identical
59+
return xParts.Length - yParts.Length;
60+
}
61+
}
62+
}

src/Microsoft.Framework.ConfigurationModel/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@
3939
[assembly: AssemblyVersion("1.0.0.0")]
4040
[assembly: AssemblyFileVersion("1.0.0.0")]
4141
[assembly: InternalsVisibleTo("Microsoft.Framework.ConfigurationModel.Test")]
42+
[assembly: InternalsVisibleTo("Microsoft.Framework.ConfigurationModel.Json.Test")]
4243
[assembly: NeutralResourcesLanguage("en-US")]
4344
[assembly: AssemblyMetadata("Serviceable", "True")]

src/Microsoft.Framework.ConfigurationModel/Sources/ConfigurationSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public virtual IEnumerable<string> ProduceSubKeys(IEnumerable<string> earlierKey
3535
return Data
3636
.Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
3737
.Select(kv => Segment(kv.Key, prefix, delimiter))
38-
.Concat(earlierKeys);
38+
.Concat(earlierKeys)
39+
.OrderBy(k => k, ConfigurationKeyComparer.Instance);
3940
}
4041

4142
private static string Segment(string key, string prefix, string delimiter)

0 commit comments

Comments
 (0)