Browse Source

Json reader.

pull/391/head
Sebastian 7 years ago
parent
commit
5e912e5fcf
  1. 2
      src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs
  2. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs
  3. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs
  4. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs
  5. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs
  6. 7
      src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs
  7. 162
      src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs
  8. 29
      src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs
  9. 4
      src/Squidex.Infrastructure/Queries/PropertyPath.cs
  10. 181
      tests/Squidex.Infrastructure.Tests/Queries/QueryJsonTests.cs
  11. 4
      tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs
  12. 3
      tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

2
src/Squidex.Infrastructure/Json/Newtonsoft/JsonClassConverter.cs

@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
{
public abstract class JsonClassConverter<T> : JsonConverter, ISupportedTypes where T : class
{
public IEnumerable<Type> SupportedTypes
public virtual IEnumerable<Type> SupportedTypes
{
get { yield return typeof(T); }
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/LanguageConverter.cs

@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
protected override Language ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var value = serializer.Deserialize<string>(reader);
return Language.GetLanguage(reader.Value.ToString());
return Language.GetLanguage(value);
}
}
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/NamedGuidIdConverter.cs

@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
protected override NamedId<Guid> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var value = serializer.Deserialize<string>(reader);
if (!NamedId<Guid>.TryParse(reader.Value.ToString(), Guid.TryParse, out var result))
if (!NamedId<Guid>.TryParse(value, Guid.TryParse, out var result))
{
throw new JsonException("Named id must have more than 2 parts divided by commata.");
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/NamedLongIdConverter.cs

@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
protected override NamedId<long> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var value = serializer.Deserialize<string>(reader);
if (!NamedId<long>.TryParse(reader.Value.ToString(), long.TryParse, out var result))
if (!NamedId<long>.TryParse(value, long.TryParse, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/NamedStringIdConverter.cs

@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
protected override NamedId<string> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var value = serializer.Deserialize<string>(reader);
if (!NamedId<string>.TryParse(reader.Value.ToString(), ParseString, out var result))
if (!NamedId<string>.TryParse(value, ParseString, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by commata.");
}

7
src/Squidex.Infrastructure/Json/Newtonsoft/RefTokenConverter.cs

@ -19,12 +19,9 @@ namespace Squidex.Infrastructure.Json.Newtonsoft
protected override RefToken ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
var value = serializer.Deserialize<string>(reader);
if (!RefToken.TryParse(reader.Value.ToString(), out var result))
if (!RefToken.TryParse(value, out var result))
{
throw new JsonException("Named id must have at least 2 parts divided by colon.");
}

162
src/Squidex.Infrastructure/Queries/Json/FilterConverter.cs

@ -0,0 +1,162 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Infrastructure.Queries.Json
{
public sealed class FilterConverter : JsonClassConverter<FilterNode<IJsonValue>>
{
public override IEnumerable<Type> SupportedTypes
{
get
{
yield return typeof(CompareFilter<IJsonValue>);
yield return typeof(FilterNode<IJsonValue>);
yield return typeof(LogicalFilter<IJsonValue>);
yield return typeof(NegateFilter<IJsonValue>);
}
}
public override bool CanConvert(Type objectType)
{
return SupportedTypes.Contains(objectType);
}
protected override FilterNode<IJsonValue> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
{
throw new JsonException($"Expected StartObject, but got {reader.TokenType}.");
}
FilterNode<IJsonValue> result = null;
var comparePath = (PropertyPath)null;
var compareOperator = (CompareOperator)99;
var compareValue = (IJsonValue)null;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
string propertyName = reader.Value.ToString();
if (!reader.Read())
{
throw new JsonSerializationException("Unexpected end when reading filter.");
}
if (result != null)
{
throw new JsonSerializationException($"Unexpected property {propertyName}");
}
switch (propertyName.ToLowerInvariant())
{
case "not":
var filter = serializer.Deserialize<FilterNode<IJsonValue>>(reader);
result = new NegateFilter<IJsonValue>(filter);
break;
case "and":
var andFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader);
result = new LogicalFilter<IJsonValue>(LogicalFilterType.And, andFilters);
break;
case "or":
var orFilters = serializer.Deserialize<List<FilterNode<IJsonValue>>>(reader);
result = new LogicalFilter<IJsonValue>(LogicalFilterType.Or, orFilters);
break;
case "path":
comparePath = serializer.Deserialize<PropertyPath>(reader);
break;
case "op":
compareOperator = ReadOperator(reader, serializer);
break;
case "value":
compareValue = serializer.Deserialize<IJsonValue>(reader);
break;
}
break;
case JsonToken.Comment:
break;
case JsonToken.EndObject:
if (result != null)
{
return result;
}
if (comparePath == null)
{
throw new JsonSerializationException("Path not defined.");
}
if (compareValue == null && compareOperator != CompareOperator.Empty)
{
throw new JsonSerializationException("Value not defined.");
}
if (!compareOperator.IsEnumValue())
{
throw new JsonSerializationException("Operator not defined.");
}
return new CompareFilter<IJsonValue>(comparePath, compareOperator, compareValue ?? JsonValue.Null);
}
}
throw new JsonSerializationException("Unexpected end when reading filter.");
}
private static CompareOperator ReadOperator(JsonReader reader, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader);
switch (value.ToLowerInvariant())
{
case "eq":
return CompareOperator.Equals;
case "ne":
return CompareOperator.NotEquals;
case "lt":
return CompareOperator.LessThan;
case "le":
return CompareOperator.LessThanOrEqual;
case "gt":
return CompareOperator.GreaterThan;
case "ge":
return CompareOperator.GreaterThanOrEqual;
case "empty":
return CompareOperator.Empty;
case "contains":
return CompareOperator.Contains;
case "endswith":
return CompareOperator.EndsWith;
case "startswith":
return CompareOperator.StartsWith;
case "in":
return CompareOperator.In;
}
throw new JsonSerializationException($"Unexpected compare operator, got {value}.");
}
protected override void WriteValue(JsonWriter writer, FilterNode<IJsonValue> value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
}

29
src/Squidex.Infrastructure/Queries/Json/PropertyPathConverter.cs

@ -0,0 +1,29 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
namespace Squidex.Infrastructure.Queries.Json
{
public sealed class PropertyPathConverter : JsonClassConverter<PropertyPath>
{
protected override PropertyPath ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{
var value = serializer.Deserialize<string>(reader);
return value;
}
protected override void WriteValue(JsonWriter writer, PropertyPath value, JsonSerializer serializer)
{
serializer.Serialize(writer, (IEnumerable<string>)value);
}
}
}

4
src/Squidex.Infrastructure/Queries/PropertyPath.cs

@ -15,6 +15,8 @@ namespace Squidex.Infrastructure.Queries
{
public sealed class PropertyPath : ReadOnlyCollection<string>
{
private static readonly char[] Separators = { '.', '/' };
public PropertyPath(IList<string> items)
: base(items)
{
@ -26,7 +28,7 @@ namespace Squidex.Infrastructure.Queries
public static implicit operator PropertyPath(string path)
{
return new PropertyPath(path?.Split('.', '/')?.ToList());
return new PropertyPath(path?.Split(Separators, StringSplitOptions.RemoveEmptyEntries)?.ToList());
}
public static implicit operator PropertyPath(string[] path)

181
tests/Squidex.Infrastructure.Tests/Queries/QueryJsonTests.cs

@ -0,0 +1,181 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Newtonsoft.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.TestHelpers;
using Xunit;
namespace Squidex.Infrastructure.Queries
{
public class QueryJsonTests
{
[Theory]
[InlineData("eq", "property == 12")]
[InlineData("ne", "property != 12")]
[InlineData("le", "property <= 12")]
[InlineData("lt", "property < 12")]
[InlineData("ge", "property >= 12")]
[InlineData("gt", "property > 12")]
[InlineData("contains", "contains(property, 12)")]
[InlineData("endswith", "endsWith(property, 12)")]
[InlineData("startswith", "startsWith(property, 12)")]
public void Should_convert_comparison(string op, string expected)
{
var json = new
{
path = "property",
op,
value = 12
};
var filter = SerializeAndDeserialize(json);
Assert.Equal(expected, filter.ToString());
}
[Fact]
public void Should_convert_comparison_empty()
{
var json = new { path = "property", op = "empty" };
var filter = SerializeAndDeserialize(json);
Assert.Equal("empty(property)", filter.ToString());
}
[Fact]
public void Should_convert_comparison_in()
{
var json = new { path = "property", op = "in", value = new[] { 12, 13 } };
var filter = SerializeAndDeserialize(json);
Assert.Equal("property in [12, 13]", filter.ToString());
}
[Fact]
public void Should_convert_comparison_with_deep_path()
{
var json = new { path = "property.nested", op = "eq", value = 12 };
var filter = SerializeAndDeserialize(json);
Assert.Equal("property.nested == 12", filter.ToString());
}
[Fact]
public void Should_convert_logical_and()
{
var json = new
{
and = new[]
{
new { path = "property", op = "ge", value = 10 },
new { path = "property", op = "lt", value = 20 }
}
};
var filter = SerializeAndDeserialize(json);
Assert.Equal("(property >= 10 && property < 20)", filter.ToString());
}
[Fact]
public void Should_convert_logical_or()
{
var json = new
{
or = new[]
{
new { path = "property", op = "ge", value = 10 },
new { path = "property", op = "lt", value = 20 }
}
};
var filter = SerializeAndDeserialize(json);
Assert.Equal("(property >= 10 || property < 20)", filter.ToString());
}
[Fact]
public void Should_convert_logical_not()
{
var json = new
{
not = new { path = "property", op = "ge", value = 10 }
};
var filter = SerializeAndDeserialize(json);
Assert.Equal("!(property >= 10)", filter.ToString());
}
[Fact]
public void Should_throw_exception_for_invalid_operator()
{
var json = new { path = "property", op = "invalid", value = 12 };
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
[Fact]
public void Should_throw_exception_for_missing_path()
{
var json = new { op = "invalid", value = 12 };
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
[Fact]
public void Should_throw_exception_for_missing_operator()
{
var json = new { path = "property", value = 12 };
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
[Fact]
public void Should_throw_exception_for_missing_value()
{
var json = new { path = "property", op = "invalid" };
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
[Fact]
public void Should_throw_exception_for_invalid_property()
{
var json = new { path = "property", op = "invalid", value = 12, other = 4 };
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
[Fact]
public void Should_throw_exception_for_invalid_property_after_filter()
{
var json = new
{
and = new[]
{
new { path = "property", op = "ge", value = 10 },
new { path = "property", op = "lt", value = 20 }
},
additional = 1
};
Assert.ThrowsAny<JsonException>(() => SerializeAndDeserialize(json));
}
private static FilterNode<IJsonValue> SerializeAndDeserialize<T>(T value)
{
var json = JsonHelper.DefaultSerializer.Serialize(value, true);
return JsonHelper.DefaultSerializer.Deserialize<FilterNode<IJsonValue>>(json);
}
}
}

4
tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs → tests/Squidex.Infrastructure.Tests/Queries/QueryODataConversionTests.cs

@ -11,11 +11,11 @@ using Xunit;
namespace Squidex.Infrastructure.Queries
{
public class ODataConversionTests
public class QueryODataConversionTests
{
private static readonly IEdmModel EdmModel;
static ODataConversionTests()
static QueryODataConversionTests()
{
var entityType = new EdmEntityType("Squidex", "Users");

3
tests/Squidex.Infrastructure.Tests/TestHelpers/JsonHelper.cs

@ -10,6 +10,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Queries.Json;
namespace Squidex.Infrastructure.TestHelpers
{
@ -34,11 +35,13 @@ namespace Squidex.Infrastructure.TestHelpers
new ClaimsPrincipalConverter(),
new InstantConverter(),
new EnvelopeHeadersConverter(),
new FilterConverter(),
new JsonValueConverter(),
new LanguageConverter(),
new NamedGuidIdConverter(),
new NamedLongIdConverter(),
new NamedStringIdConverter(),
new PropertyPathConverter(),
new RefTokenConverter(),
new StringEnumConverter()),

Loading…
Cancel
Save