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

Commit

Permalink
- Support for JSON arrays
Browse files Browse the repository at this point in the history
- Use the json.net parser instead of manually parsing the json
- Simplified some test code
  • Loading branch information
Victor Hurdugaci committed Apr 28, 2015
1 parent 7d42e66 commit 0517eee
Show file tree
Hide file tree
Showing 24 changed files with 770 additions and 331 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.Framework.ConfigurationModel.Json
{
internal class JsonConfigurationFileParser
{
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> _context = new Stack<string>();
private string _currentPath;

private JsonTextReader _reader;

public IDictionary<string, string> Parse(Stream input)
{
_data.Clear();
_reader = new JsonTextReader(new StreamReader(input));
_reader.DateParseHandling = DateParseHandling.None;

var jsonConfig = JObject.Load(_reader);

VisitJObject(jsonConfig);

return _data;
}

private void VisitJObject(JObject jObject)
{
foreach (var property in jObject.Properties())
{
EnterContext(property.Name);
VisitProperty(property);
ExitContext();
}
}

private void VisitProperty(JProperty property)
{
VisitToken(property.Value);
}

private void VisitToken(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
VisitJObject(token.Value<JObject>());
break;

case JTokenType.Array:
VisitArray(token.Value<JArray>());
break;

case JTokenType.Integer:
case JTokenType.Float:
case JTokenType.String:
case JTokenType.Boolean:
case JTokenType.Bytes:
case JTokenType.Raw:
case JTokenType.Null:
VisitPrimitive(token);
break;

default:
throw new FormatException(Resources.FormatError_UnsupportedJSONToken(
_reader.TokenType,
_reader.Path,
_reader.LineNumber,
_reader.LinePosition));
}
}

private void VisitArray(JArray array)
{
for (int index = 0; index < array.Count; index++)
{
EnterContext(index.ToString());
VisitToken(array[index]);
ExitContext();
}
}

private void VisitPrimitive(JToken data)
{
var key = _currentPath;

if (_data.ContainsKey(key))
{
throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
}
_data[key] = data.ToString();
}

private void EnterContext(string context)
{
_context.Push(context);
_currentPath = string.Join(":", _context.Reverse());
}

private void ExitContext()
{
_context.Pop();
_currentPath = string.Join(":", _context.Reverse());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.Framework.ConfigurationModel.Json;
using Newtonsoft.Json;

namespace Microsoft.Framework.ConfigurationModel
{
Expand Down Expand Up @@ -78,129 +77,8 @@ public override void Load()

internal void Load(Stream stream)
{
var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

using (var reader = new JsonTextReader(new StreamReader(stream)))
{
var startObjectCount = 0;

// Dates are parsed as strings
reader.DateParseHandling = DateParseHandling.None;

// Move to the first token
reader.Read();

SkipComments(reader);

if (reader.TokenType != JsonToken.StartObject)
{
throw new FormatException(Resources.FormatError_RootMustBeAnObject(reader.Path,
reader.LineNumber, reader.LinePosition));
}

do
{
SkipComments(reader);

switch (reader.TokenType)
{
case JsonToken.StartObject:
startObjectCount++;
break;

case JsonToken.EndObject:
startObjectCount--;
break;

// Keys in key-value pairs
case JsonToken.PropertyName:
break;

// Values in key-value pairs
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Bytes:
case JsonToken.Raw:
case JsonToken.Null:
var key = GetKey(reader.Path);

if (data.ContainsKey(key))
{
throw new FormatException(Resources.FormatError_KeyIsDuplicated(key));
}
data[key] = reader.Value.ToString();
break;

// End of file
case JsonToken.None:
{
throw new FormatException(Resources.FormatError_UnexpectedEnd(reader.Path,
reader.LineNumber, reader.LinePosition));
}

default:
{
// Unsupported elements: Array, Constructor, Undefined
throw new FormatException(Resources.FormatError_UnsupportedJSONToken(
reader.TokenType, reader.Path, reader.LineNumber, reader.LinePosition));
}
}

reader.Read();

} while (startObjectCount > 0);
}

Data = data;
}

private string GetKey(string jsonPath)
{
var pathSegments = new List<string>();
var index = 0;

while (index < jsonPath.Length)
{
// If the JSON element contains '.' in its name, JSON.net escapes that element as ['element']
// while getting its Path. So before replacing '.' => ':' to represent JSON hierarchy, here
// we skip a '.' => ':' conversion if the element is not enclosed with in ['..'].
var start = jsonPath.IndexOf("['", index);

if (start < 0)
{
// No more ['. Skip till end of string.
pathSegments.Add(jsonPath.
Substring(index).
Replace('.', ':'));
break;
}
else
{
if (start > index)
{
pathSegments.Add(
jsonPath
.Substring(index, start - index) // Anything between the previous [' and '].
.Replace('.', ':'));
}

var endIndex = jsonPath.IndexOf("']", start);
pathSegments.Add(jsonPath.Substring(start + 2, endIndex - start - 2));
index = endIndex + 2;
}
}

return string.Join(string.Empty, pathSegments);
}

private void SkipComments(JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment)
{
reader.Read();
}
JsonConfigurationFileParser parser = new JsonConfigurationFileParser();
Data = parser.Parse(stream);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,6 @@
<data name="Error_KeyIsDuplicated" xml:space="preserve">
<value>A duplicate key '{0}' was found.</value>
</data>
<data name="Error_RootMustBeAnObject" xml:space="preserve">
<value>Only an object can be the root. Path '{0}', line {1} position {2}.</value>
</data>
<data name="Error_UnexpectedEnd" xml:space="preserve">
<value>Unexpected end when parsing JSON. Path '{0}', line {1} position {2}.</value>
</data>
<data name="Error_UnsupportedJSONToken" xml:space="preserve">
<value>Unsupported JSON token '{0}' was found. Path '{1}', line {2} position {3}.</value>
</data>
Expand Down
6 changes: 5 additions & 1 deletion src/Microsoft.Framework.ConfigurationModel.Json/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"frameworks": {
"net45": { },
"dnx451": { },
"dnxcore50": { }
"dnxcore50": {
"dependencies": {
"System.Dynamic.Runtime": "4.0.10-*"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;

namespace Microsoft.Framework.ConfigurationModel
{
public class ConfigurationKeyComparer : IComparer<string>
{
private const char Separator = ':';

public static ConfigurationKeyComparer Instance { get; } = new ConfigurationKeyComparer();

public int Compare(string x, string y)
{
var xParts = x?.Split(Separator) ?? new string[0];
var yParts = y?.Split(Separator) ?? new string[0];

// Compare each part until we get two parts that are not equal
for (int i = 0; i < Math.Min(xParts.Length, yParts.Length); i++)
{
x = xParts[i];
y = yParts[i];

var value1 = 0;
var value2 = 0;

var xIsInt = x != null && int.TryParse(x, out value1);
var yIsInt = y != null && int.TryParse(y, out value2);

int result = 0;

if (!xIsInt && !yIsInt)
{
// Both are strings
result = string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
}
else if (xIsInt && yIsInt)
{
// Both are int
result = value1 - value2;
}
else
{
// Only one of them is int
result = xIsInt ? -1 : 1;
}

if (result != 0)
{
// One of them is different
return result;
}
}

// If we get here, the common parts are equal.
// If they are of the same length, then they are totally identical
return xParts.Length - yParts.Length;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: InternalsVisibleTo("Microsoft.Framework.ConfigurationModel.Test")]
[assembly: InternalsVisibleTo("Microsoft.Framework.ConfigurationModel.Json.Test")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyMetadata("Serviceable", "True")]
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public virtual IEnumerable<string> ProduceSubKeys(IEnumerable<string> earlierKey
return Data
.Where(kv => kv.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
.Select(kv => Segment(kv.Key, prefix, delimiter))
.Concat(earlierKeys);
.Concat(earlierKeys)
.OrderBy(k => k, ConfigurationKeyComparer.Instance);
}

private static string Segment(string key, string prefix, string delimiter)
Expand Down
Loading

0 comments on commit 0517eee

Please sign in to comment.