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 abstract class JsonClassConverter<T> : JsonConverter, ISupportedTypes where T : class
{ {
public IEnumerable<Type> SupportedTypes public virtual IEnumerable<Type> SupportedTypes
{ {
get { yield return typeof(T); } 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) protected override Language ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
if (reader.TokenType != JsonToken.String) var value = serializer.Deserialize<string>(reader);
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
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) protected override NamedId<Guid> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
if (reader.TokenType != JsonToken.String) var value = serializer.Deserialize<string>(reader);
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
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."); 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) protected override NamedId<long> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
if (reader.TokenType != JsonToken.String) var value = serializer.Deserialize<string>(reader);
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
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."); 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) protected override NamedId<string> ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
if (reader.TokenType != JsonToken.String) var value = serializer.Deserialize<string>(reader);
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
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."); 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) protected override RefToken ReadValue(JsonReader reader, Type objectType, JsonSerializer serializer)
{ {
if (reader.TokenType != JsonToken.String) var value = serializer.Deserialize<string>(reader);
{
throw new JsonException($"Expected String, but got {reader.TokenType}.");
}
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."); 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> public sealed class PropertyPath : ReadOnlyCollection<string>
{ {
private static readonly char[] Separators = { '.', '/' };
public PropertyPath(IList<string> items) public PropertyPath(IList<string> items)
: base(items) : base(items)
{ {
@ -26,7 +28,7 @@ namespace Squidex.Infrastructure.Queries
public static implicit operator PropertyPath(string path) 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) 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 namespace Squidex.Infrastructure.Queries
{ {
public class ODataConversionTests public class QueryODataConversionTests
{ {
private static readonly IEdmModel EdmModel; private static readonly IEdmModel EdmModel;
static ODataConversionTests() static QueryODataConversionTests()
{ {
var entityType = new EdmEntityType("Squidex", "Users"); 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 Newtonsoft.Json.Converters;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Newtonsoft; using Squidex.Infrastructure.Json.Newtonsoft;
using Squidex.Infrastructure.Queries.Json;
namespace Squidex.Infrastructure.TestHelpers namespace Squidex.Infrastructure.TestHelpers
{ {
@ -34,11 +35,13 @@ namespace Squidex.Infrastructure.TestHelpers
new ClaimsPrincipalConverter(), new ClaimsPrincipalConverter(),
new InstantConverter(), new InstantConverter(),
new EnvelopeHeadersConverter(), new EnvelopeHeadersConverter(),
new FilterConverter(),
new JsonValueConverter(), new JsonValueConverter(),
new LanguageConverter(), new LanguageConverter(),
new NamedGuidIdConverter(), new NamedGuidIdConverter(),
new NamedLongIdConverter(), new NamedLongIdConverter(),
new NamedStringIdConverter(), new NamedStringIdConverter(),
new PropertyPathConverter(),
new RefTokenConverter(), new RefTokenConverter(),
new StringEnumConverter()), new StringEnumConverter()),

Loading…
Cancel
Save