-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathExpressionConverter.cs
More file actions
152 lines (133 loc) · 5.38 KB
/
ExpressionConverter.cs
File metadata and controls
152 lines (133 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
using System.Text.Json;
using System.Text.Json.Serialization;
using Altinn.App.Core.Internal.Expressions;
namespace Altinn.App.Core.Models.Expressions;
/// <summary>
/// JsonConverter to be able to parse any valid Expression in Json format to the C# <see cref="Expression"/>
/// </summary>
/// <remarks>
/// Currently this parser supports {"function":"funcname", "args": [arg1, arg2]} and ["funcname", arg1, arg2] syntax, and literal primitive types
/// </remarks>
public class ExpressionConverter : JsonConverter<Expression>
{
/// <inheritdoc />
public override Expression Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return ReadStatic(ref reader, options);
}
/// <summary>
/// Reads a JSON element and converts it to an <see cref="Expression"/>.
/// </summary>
public static Expression ReadStatic(JsonElement element) =>
element.ValueKind switch
{
JsonValueKind.True => new Expression(true),
JsonValueKind.False => new Expression(false),
JsonValueKind.String => new Expression(element.GetString()),
JsonValueKind.Number => new Expression(element.GetDecimal()),
JsonValueKind.Null => new Expression(ExpressionValue.Null),
JsonValueKind.Array => ReadArray(element),
JsonValueKind.Object => throw new JsonException("Invalid type \"object\""),
_ => throw new JsonException(),
};
/// <summary>
/// Same as <see cref="Read" />, but without the nullable return type required by the interface. Throw an exeption instead.
/// </summary>
public static Expression ReadStatic(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
return reader.TokenType switch
{
JsonTokenType.True => new Expression(true),
JsonTokenType.False => new Expression(false),
JsonTokenType.String => new Expression(reader.GetString()),
JsonTokenType.Number => new Expression(reader.GetDecimal()),
JsonTokenType.Null => new Expression(ExpressionValue.Null),
JsonTokenType.StartArray => ReadArray(ref reader, options),
JsonTokenType.StartObject => throw new JsonException("Invalid type \"object\""),
_ => throw new JsonException(),
};
}
private static Expression ReadArray(JsonElement element)
{
if (element.GetArrayLength() == 0)
{
throw new JsonException("Missing function name in expression");
}
using var enumerator = element.EnumerateArray();
if (!enumerator.MoveNext())
{
throw new JsonException("Missing function name in expression");
}
if (enumerator.Current.ValueKind != JsonValueKind.String)
{
throw new JsonException("Function name in expression must be string");
}
var args = new List<Expression>();
var functionString = enumerator.Current.GetString();
var functionEnum = ExpressionFunction(functionString);
if (functionEnum == Expressions.ExpressionFunction.INVALID)
{
args.Add(new Expression(functionString));
}
while (enumerator.MoveNext())
{
args.Add(ReadStatic(enumerator.Current));
}
return new Expression(functionEnum, args.ToArray());
}
private static Expression ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndArray)
{
throw new JsonException("Missing function name in expression");
}
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException("Function name in expression should be string");
}
var args = new List<Expression>();
var functionString = reader.GetString();
var functionEnum = ExpressionFunction(functionString);
if (functionEnum == Expressions.ExpressionFunction.INVALID)
{
args.Add(new Expression(functionString));
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
args.Add(ReadStatic(ref reader, options));
}
return new Expression(functionEnum, args.ToArray());
}
private static ExpressionFunction ExpressionFunction(string? stringFunction)
{
if (!Enum.TryParse<ExpressionFunction>(stringFunction, ignoreCase: false, out var functionEnum))
{
return Expressions.ExpressionFunction.INVALID;
}
return functionEnum;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Expression value, JsonSerializerOptions options)
{
if (value.IsLiteralValue)
{
// Just serialize the literal value
JsonSerializer.Serialize(writer, value.ValueUnion.ToObject(), options);
}
else
{
// Serialize with as an array expression ["functionName", arg1, arg2, ...]
writer.WriteStartArray();
if (value.Function != Expressions.ExpressionFunction.INVALID)
{
writer.WriteStringValue(value.Function.ToString());
}
foreach (var arg in value.Args)
{
JsonSerializer.Serialize(writer, arg, options);
}
writer.WriteEndArray();
}
}
}