/
ReduceRule.cs
129 lines (109 loc) · 3.7 KB
/
ReduceRule.cs
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
using System;
using System.Runtime.InteropServices.ComTypes;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Json.More;
namespace Json.Logic.Rules;
/// <summary>
/// Handles the `reduce` operation.
/// </summary>
[Operator("reduce")]
[JsonConverter(typeof(ReduceRuleJsonConverter))]
public class ReduceRule : Rule, IRule
{
/// <summary>
/// A sequence of values to reduce.
/// </summary>
protected internal Rule Input { get; }
/// <summary>
/// The reduction to perform.
/// </summary>
protected internal Rule Rule { get; }
/// <summary>
/// The initial value to start the reduction. ie; the seed.
/// </summary>
protected internal Rule Initial { get; }
/// <summary>
/// Creates a new instance of <see cref="ReduceRule"/> when 'reduce' operator is detected within json logic.
/// </summary>
/// <param name="input">A sequence of values to reduce.</param>
/// <param name="rule">The reduction to perform.</param>
/// <param name="initial">The initial value to start the reduction. ie; the seed.</param>
protected internal ReduceRule(Rule input, Rule rule, Rule initial)
{
Input = input;
Rule = rule;
Initial = initial;
}
internal ReduceRule(){}
/// <summary>
/// Applies the rule to the input data.
/// </summary>
/// <param name="data">The input data.</param>
/// <param name="contextData">
/// Optional secondary data. Used by a few operators to pass a secondary
/// data context to inner operators.
/// </param>
/// <returns>The result of the rule.</returns>
public override JsonNode? Apply(JsonNode? data, JsonNode? contextData = null)
{
var input = Input.Apply(data, contextData);
var accumulator = Initial.Apply(data, contextData);
if (input is not JsonArray arr) return accumulator;
foreach (var element in arr)
{
var intermediary = new JsonObject
{
["current"] = element?.DeepClone(),
["accumulator"] = accumulator?.DeepClone()
};
accumulator = Rule.Apply(data, intermediary);
if (accumulator == null) break;
}
return accumulator;
}
JsonNode? IRule.Apply(JsonNode? args, EvaluationContext context)
{
if (args is not JsonArray{Count:3} array)
throw new JsonException("The 'reduce' rule needs an array with 3 parameters.");
var input = JsonLogic.Apply(array[0], context);
var rule = array[1];
var accumulator = JsonLogic.Apply(array[2], context);
if (input is not JsonArray arr) return accumulator;
foreach (var element in arr)
{
var intermediary = new JsonObject
{
["current"] = element?.DeepClone(),
["accumulator"] = accumulator?.DeepClone()
};
context.Push(intermediary);
accumulator = JsonLogic.Apply(rule, context);
context.Pop();
if (accumulator == null) break;
}
return accumulator;
}
}
internal class ReduceRuleJsonConverter : WeaklyTypedJsonConverter<ReduceRule>
{
public override ReduceRule? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var parameters = options.ReadArray(ref reader, JsonLogicSerializerContext.Default.Rule);
if (parameters is not { Length: 3 })
throw new JsonException("The reduce rule needs an array with 3 parameters.");
return new ReduceRule(parameters[0], parameters[1], parameters[2]);
}
public override void Write(Utf8JsonWriter writer, ReduceRule value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("reduce");
writer.WriteStartArray();
options.Write(writer, value.Input, JsonLogicSerializerContext.Default.Rule);
options.Write(writer, value.Rule, JsonLogicSerializerContext.Default.Rule);
options.Write(writer, value.Initial, JsonLogicSerializerContext.Default.Rule);
writer.WriteEndArray();
writer.WriteEndObject();
}
}