Skip to content

Commit 0f87383

Browse files
authored
Merge pull request #44 from g4-api/development
Add ReadJson plugin for JSON extraction and Base64 output
2 parents 862a618 + 0270954 commit 0f87383

4 files changed

Lines changed: 525 additions & 0 deletions

File tree

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
{
2+
"author": {
3+
"link": "https://www.linkedin.com/in/roei-sabag-247aa18/",
4+
"name": "Roei Sabag"
5+
},
6+
"categories": [
7+
"DataExtraction"
8+
],
9+
"context": {
10+
"integration": {
11+
"github": {
12+
"document": "https://github.com/g4-api/g4-plugins/blob/main/docs/Actions/ReadJson.md",
13+
"source": "https://github.com/g4-api/g4-plugins/blob/main/src/G4.Plugins.Common/Actions/ReadJson.cs"
14+
},
15+
"rag": {
16+
"description": "The ReadJson plugin reliably extracts targeted data from JSON sources inside automation workflows. It enforces strict JSON parsing, supports dynamic JSONPath selection and optional regular expression filtering, normalizes single or multiple matches, and encodes results to Base64. This preserves workflow reliability while enabling flexible, reusable data extraction.",
17+
"qa": [
18+
{
19+
"question": "What is the ReadJson plugin?",
20+
"answer": "ReadJson is an action plugin that reads JSON from a file path or inline content, extracts selected values using JSONPath, optionally filters them with a regular expression, and outputs the result as a Base64-encoded string."
21+
},
22+
{
23+
"question": "What problem does ReadJson solve in automation workflows?",
24+
"answer": "It removes the need for custom code when extracting structured values from JSON, making workflows easier to maintain, more reliable, and simpler to reuse."
25+
},
26+
{
27+
"question": "What input sources does ReadJson support?",
28+
"answer": "The plugin accepts either a file path to a JSON file or raw inline JSON text provided through the Argument property."
29+
},
30+
{
31+
"question": "How does the OnElement property work?",
32+
"answer": "OnElement defines a JSONPath expression that selects which parts of the JSON structure should be extracted for further processing."
33+
},
34+
{
35+
"question": "What happens if the JSONPath expression matches multiple values?",
36+
"answer": "All matched values are serialized into a JSON array before any further filtering or encoding is applied."
37+
},
38+
{
39+
"question": "What happens if the JSONPath expression matches a single value?",
40+
"answer": "Only the matched value is serialized as JSON instead of wrapping it in an array."
41+
},
42+
{
43+
"question": "What is the purpose of the RegularExpression property?",
44+
"answer": "RegularExpression optionally filters the serialized JSON output to isolate a specific portion of the extracted content before encoding."
45+
},
46+
{
47+
"question": "Is the RegularExpression property mandatory?",
48+
"answer": "No. If it is omitted or empty, the serialized JSON result is encoded directly without additional filtering."
49+
},
50+
{
51+
"question": "Why does ReadJson encode results to Base64?",
52+
"answer": "Base64 encoding ensures the extracted value can be safely stored, transported, and reused without formatting or character issues."
53+
},
54+
{
55+
"question": "Where is the extracted result stored?",
56+
"answer": "The result is saved into the session parameter named ReadJsonResult for use by downstream plugins."
57+
},
58+
{
59+
"question": "What output does the plugin expose directly in its response?",
60+
"answer": "The plugin exposes the extracted Base64-encoded value through the JsonResult field in the response entity."
61+
},
62+
{
63+
"question": "What are best practices when using ReadJson?",
64+
"answer": "Use precise JSONPath expressions, validate JSON input early, apply a regular expression only when necessary, and reference ReadJsonResult consistently in later steps."
65+
},
66+
{
67+
"question": "What manifest metadata defines the ReadJson plugin?",
68+
"answer": "ReadJson has the key ReadJson, manifestVersion 4, pluginType Action, category DataExtraction, and supports the Any platform."
69+
},
70+
{
71+
"question": "Who is the author of the ReadJson plugin?",
72+
"answer": "The plugin is authored by Roei Sabag."
73+
},
74+
{
75+
"question": "Where can I find the source code for ReadJson?",
76+
"answer": "The source code is available at https://github.com/g4-api/g4-plugins/blob/main/src/G4.Plugins.Common/Actions/ReadJson.cs."
77+
},
78+
{
79+
"question": "Where can I find the official documentation for ReadJson?",
80+
"answer": "The official documentation is available at https://github.com/g4-api/g4-plugins/blob/main/docs/Actions/ReadJson.md."
81+
},
82+
{
83+
"question": "How does ReadJson handle missing matches or edge cases?",
84+
"answer": "If no JSONPath tokens or regex matches are found, the plugin returns an empty result while keeping the workflow execution stable."
85+
}
86+
]
87+
},
88+
"sequentialWorkflow": {
89+
"$type": "Action",
90+
"componentType": "task",
91+
"iconProvider": "file-import",
92+
"model": "ActionRuleModel"
93+
}
94+
}
95+
},
96+
"description": [
97+
"### Purpose",
98+
"",
99+
"The ReadJson plugin extracts specific values from JSON data in a simple and reliable way.",
100+
"It can read JSON either from a file path or directly from inline content.",
101+
"The plugin helps automation workflows reuse structured data without custom code.",
102+
"It is useful when JSON responses need to be filtered, transformed, or passed to later steps.",
103+
"",
104+
"### Key Features and Functionality",
105+
"",
106+
"| Feature | Description |",
107+
"|--------------------------------|----------------------------------------------------------------------------------|",
108+
"| File or Inline JSON Input | Reads JSON from a file if the path exists, or treats the input as raw JSON text. |",
109+
"| JSONPath Selection | Extracts one or more values using a JSONPath expression. |",
110+
"| Automatic Result Normalization | Returns a single object or an array, depending on how many values are matched. |",
111+
"| Regular Expression Filtering | Applies an optional regular expression to the extracted JSON content. |",
112+
"| Base64 Encoding | Encodes the final extracted value to Base64 for safe transport and storage. |",
113+
"| Session Storage | Saves the result in session parameters for use by downstream plugins. |",
114+
"",
115+
"### Usages in RPA",
116+
"",
117+
"| Use Case | Description |",
118+
"|----------------------------|-------------------------------------------------------------------------|",
119+
"| API Response Parsing | Extract specific fields from API JSON responses during automation runs. |",
120+
"| Configuration Reading | Read values from JSON configuration files used by bots. |",
121+
"| Dynamic Decision Making | Use extracted JSON values to drive conditional workflow logic. |",
122+
"| Data Passing Between Steps | Store JSON-derived values in session parameters for later actions. |",
123+
"",
124+
"### Usages in Automation Testing",
125+
"",
126+
"| Use Case | Description |",
127+
"|-----------------------|---------------------------------------------------------------|",
128+
"| Test Data Extraction | Read expected values from JSON test data files. |",
129+
"| API Validation | Extract and validate fields from JSON API responses in tests. |",
130+
"| Assertion Preparation | Prepare filtered JSON values for comparison and assertions. |",
131+
"| Test Context Sharing | Share parsed JSON values across multiple test steps. |"
132+
],
133+
"entity": [
134+
{
135+
"description": [
136+
"Contains the extracted value produced from the selected JSON content.",
137+
"The value represents the portion of JSON matched by the optional regular expression.",
138+
"The result is stored as a Base64-encoded string for safe transport and reuse."
139+
],
140+
"name": "JsonResult",
141+
"type": "String"
142+
}
143+
],
144+
"examples": [
145+
{
146+
"context": {
147+
"annotations": {
148+
"edge_cases": [
149+
"Empty JSON document",
150+
"Invalid JSONPath expression",
151+
"JSONPath returns multiple tokens",
152+
"JSONPath returns no tokens",
153+
"Regular expression does not match",
154+
"Single JSON token result"
155+
],
156+
"expected_result": "The selected JSON value is extracted, optionally filtered by a regular expression, converted to Base64, and stored in the session parameter ReadJsonResult.",
157+
"notes": "The Argument value may be either a file path or inline JSON. The action parses the JSON, applies the JSONPath expression from OnElement, serializes the result, applies the regular expression if provided, and Base64-encodes the matched value.",
158+
"use_case": "read_json_single_property",
159+
"version": "10.0.0"
160+
},
161+
"labels": [
162+
"Base64Encoding",
163+
"DataExtraction",
164+
"Json",
165+
"JsonPath",
166+
"RegularExpression"
167+
]
168+
},
169+
"description": [
170+
"### Extract a single JSON property",
171+
"",
172+
"Provide inline JSON content through the `argument` property.",
173+
"Select a single value using a JSONPath expression defined in `OnElement`.",
174+
"The selected value is serialized as JSON before further processing.",
175+
"A regular expression `\"[^\"]+\"` is applied to the value attribute to extract the inner string.",
176+
"The extracted value is converted to Base64 and stored in the session parameter `ReadJsonResult`."
177+
],
178+
"rule": {
179+
"$type": "Action",
180+
"argument": "{\"name\":\"alpha\",\"count\":3}",
181+
"onElement": "$.name",
182+
"pluginName": "ReadJson",
183+
"regularExpression": "\"[^\"]+\""
184+
}
185+
},
186+
{
187+
"context": {
188+
"annotations": {
189+
"edge_cases": [
190+
"File path does not exist",
191+
"JSON array contains multiple objects",
192+
"JSONPath selects multiple fields",
193+
"Regular expression matches partially",
194+
"Whitespace differences in serialized JSON"
195+
],
196+
"expected_result": "All matched JSONPath values are serialized as a JSON array, processed by the regular expression, converted to Base64, and saved to the session parameter ReadJsonResult.",
197+
"notes": "When multiple tokens are returned by the JSONPath expression, the action serializes the full array instead of a single object before applying the regular expression.",
198+
"use_case": "read_json_multiple_values",
199+
"version": "10.0.0"
200+
},
201+
"labels": [
202+
"Base64Encoding",
203+
"CollectionHandling",
204+
"DataExtraction",
205+
"Json",
206+
"JsonPath",
207+
"RegularExpression"
208+
]
209+
},
210+
"description": [
211+
"### Extract multiple values from a JSON array",
212+
"",
213+
"Load JSON content from a file path provided in the `argument` property.",
214+
"Use a JSONPath expression in `OnElement` to select multiple values.",
215+
"The selected tokens are serialized into a JSON array.",
216+
"A regular expression `\\d+` is applied to the value attribute to extract numeric content.",
217+
"The matched value is converted to Base64 and written to the session parameter `ReadJsonResult`."
218+
],
219+
"rule": {
220+
"$type": "Action",
221+
"argument": "data.json",
222+
"onElement": "$.items[*].id",
223+
"pluginName": "ReadJson",
224+
"regularExpression": "\\d+"
225+
}
226+
},
227+
{
228+
"context": {
229+
"annotations": {
230+
"edge_cases": [
231+
"Missing regularExpression property",
232+
"JSONPath matches nested objects",
233+
"Large JSON payload",
234+
"Special characters in JSON strings"
235+
],
236+
"expected_result": "The serialized JSON result is converted directly to Base64 when no regular expression is provided and stored in the session parameter ReadJsonResult.",
237+
"notes": "If the regularExpression value is empty or omitted, the full serialized JSON output is used as the input for Base64 conversion.",
238+
"use_case": "read_json_without_regex",
239+
"version": "10.0.0"
240+
},
241+
"labels": [
242+
"Base64Encoding",
243+
"DataExtraction",
244+
"Json",
245+
"JsonPath"
246+
]
247+
},
248+
"description": [
249+
"### Extract JSON data without a regular expression",
250+
"",
251+
"Supply inline JSON using the `argument` property.",
252+
"Select a nested object using the JSONPath expression defined in `OnElement`.",
253+
"The selected result is serialized as JSON.",
254+
"No filtering is applied because no regular expression is provided.",
255+
"The serialized JSON is converted to Base64 and saved in the session parameter `ReadJsonResult`."
256+
],
257+
"rule": {
258+
"$type": "Action",
259+
"argument": "{\"config\":{\"enabled\":true,\"level\":\"high\"}}",
260+
"onElement": "$.config",
261+
"pluginName": "ReadJson"
262+
}
263+
}
264+
],
265+
"key": "ReadJson",
266+
"manifestVersion": 4,
267+
"OutputParameters": [
268+
{
269+
"description": [
270+
"The session parameter that holds the extracted JSON value.",
271+
"The parameter contains the final result produced from the selected JSON content.",
272+
"Its value can be referenced by other parameters or expressions in the workflow."
273+
],
274+
"mandatory": true,
275+
"name": "ReadJsonResult",
276+
"type": "String"
277+
}
278+
],
279+
"platforms": [
280+
"Any"
281+
],
282+
"pluginType": "Action",
283+
"properties": [
284+
{
285+
"description": [
286+
"Provides the JSON input to be read and processed by the plugin.",
287+
"The value can be either a file path pointing to a JSON file or raw JSON content provided inline.",
288+
"This flexibility allows workflows to read JSON from disk or from dynamically generated data."
289+
],
290+
"mandatory": true,
291+
"name": "Argument",
292+
"type": "String|Expression"
293+
},
294+
{
295+
"description": [
296+
"Defines the JSONPath expression used to select specific elements from the input JSON.",
297+
"The expression controls which parts of the JSON structure are extracted for further processing.",
298+
"Using a precise path ensures only the relevant data is read and passed to later steps."
299+
],
300+
"mandatory": true,
301+
"name": "OnElement",
302+
"type": "String"
303+
},
304+
{
305+
"description": [
306+
"Specifies an optional regular expression applied to the extracted JSON content.",
307+
"The expression is used to match and isolate a specific portion of the selected JSON.",
308+
"This allows fine-grained filtering before the result is converted and stored."
309+
],
310+
"mandatory": false,
311+
"name": "RegularExpression",
312+
"type": "Regex"
313+
}
314+
],
315+
"protocol": {
316+
"apiDocumentation": "None",
317+
"w3c": "None"
318+
},
319+
"summary": [
320+
"The ReadJson plugin reads JSON data from a file path or from inline text and parses it for querying.",
321+
"It extracts values using a JSONPath expression and can further filter the result with a regular expression.",
322+
"The extracted value is converted to Base64 and stored for reuse in later workflow steps."
323+
]
324+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using G4.Attributes;
2+
using G4.Extensions;
3+
using G4.Models;
4+
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Text.RegularExpressions;
12+
13+
namespace G4.Plugins.Common.Actions
14+
{
15+
[G4Plugin(
16+
assembly: "G4.Plugins.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null",
17+
manifest: $"G4.Plugins.Common.Actions.Manifests.{nameof(ReadJson)}.json")]
18+
public class ReadJson(G4PluginSetupModel pluginSetup) : PluginBase(pluginSetup)
19+
{
20+
protected override PluginResponseModel OnSend(PluginDataModel pluginData)
21+
{
22+
// Determine whether the argument is a file path or raw JSON content.
23+
// If the file exists, read its contents; otherwise, treat the argument as inline JSON.
24+
var jsonData = File.Exists(pluginData.Rule.Argument)
25+
? File.ReadAllText(pluginData.Rule.Argument)
26+
: pluginData.Rule.Argument;
27+
28+
// Parse the JSON into a JObject for querying.
29+
var json = JObject.Parse(jsonData);
30+
31+
// Select tokens using the provided JSONPath expression.
32+
var selected = json.SelectTokens(pluginData.Rule.OnElement);
33+
34+
// Convert the selected tokens into a JSON array for uniform handling.
35+
var array = JArray.FromObject(selected);
36+
37+
// If only a single item is found, serialize just that object;
38+
// otherwise, serialize the full array.
39+
var input = array.Count == 1
40+
? array.First().ToString(Formatting.None).Trim('"')
41+
: array.ToString(Formatting.None).Trim('"');
42+
43+
// Apply the regular expression (if provided) to the serialized JSON
44+
// and convert the matched value to Base64.
45+
// If no match is found, return an empty string.
46+
var jsonResult = Regex
47+
.Match(input, pattern: pluginData.Rule.RegularExpression)?
48+
.Value
49+
.ConvertToBase64() ?? string.Empty;
50+
51+
// Store the result in the session context for downstream plugins.
52+
Invoker.Context.SessionParameters["ReadJsonResult"] = jsonResult;
53+
54+
// Create a new plugin response.
55+
var pluginResponse = this.NewPluginResponse();
56+
57+
// Expose the result in the response entity.
58+
pluginResponse.Entity = new Dictionary<string, object>
59+
{
60+
["JsonResult"] = jsonResult
61+
};
62+
63+
// Return the plugin response to the framework.
64+
return pluginResponse;
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)