Dynamically retrieve variable and argument values for NativeActivity - c#

When I run the below code (C#) as a custom activity (compiled .dll is added to a nuget package and triggered in a UiPath sequence with some user defined variables/arguments. I am able to retrieve the name of the variable and it's type, but I cannot find the proper syntax to retrieve the value (I just want to convert it to a String, no need to do anything fancy). I can access some of the properties, so i know i am close. I have done my best to read the docs, but in this instance, it may be a little two abstract for me. I have gone through many interations and scoured as much of the internet as I can, but I cannot seem to figure it out.
using Newtonsoft.Json.Linq;
using System;
using System.Activities;
using System.Activities.Hosting;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
namespace AutoLog
{
public sealed class GetRootActivity : NativeActivity
{
public OutArgument<string> variables { get; set; }
protected override void Execute(NativeActivityContext context)
{
this.variables.Set((ActivityContext)context, Library.getLocalVariables(context));
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
metadata.AddDefaultExtensionProvider<GetRootActivity.WorkflowInstanceInfo>((Func<GetRootActivity.WorkflowInstanceInfo>)(() => new GetRootActivity.WorkflowInstanceInfo()));
}
public class Library
{
public static string getLocalVariables(NativeActivityContext context)
{
var properties = context.DataContext.GetProperties();
JArray variables = new JArray();
foreach(PropertyDescriptor p in properties)
{
JObject variable = new JObject();
variable["name"] = p.Name;
variable["type"] = p.PropertyType.ToString();
string string_value = "";
try
{
var myValue = context.DataContext.GetType().GetProperty(p.Name).GetValue(context.DataContext, null);
string_value = myValue.ToString();
}
catch(Exception e)
{
}
variable["value"] = string_value;
variables.Add(variable);
}
return variables.ToString();
}
}
}
}
Below is an example of the JSON it generates, as you can see, the "value" field is empty
[
{
"name": "a",
"type": "System.String",
"value": ""
},
{
"name": "b",
"type": "System.Boolean",
"value": ""
},
{
"name": "c",
"type": "System.Int32",
"value": ""
},
{
"name": "test",
"type": "System.String",
"value": ""
},
{
"name": "f",
"type": "System.String",
"value": ""
}
]

var myValue = context.DataContext.GetType().GetProperty(p.Name).GetValue(context.DataContext, null);
string_value = myValue.ToString();
can be changed to
string_value = p.GetValue(context.DataContext) as String;
I had previously tried this approach but got trying to make the cast dynamic, and apparently I had never tried the simpler solution.

Related

Parsing JSON and applying filter

I've ObservableCollection<dynamic> called myJSON in C# having 4 items as follows:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
I need to be able to apply a filter query like say where name="A" and location="NY" and then get back 2 records from above.
I tried code like below but I was only able to parse one record at a time from the above collection. And also the 2nd line seems to error out with a message:
"Cannot access child value on Newtonsoft.Json.Linq.JValue."
JObject json = JObject.Parse(myJSON[0].ToString());
var match = json.Values<JProperty>().Where(m => m.Value["name"].ToString() == "A" && m.Value["location"].ToString() == "NY").FirstOrDefault();
Thanks.
Here:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace NameLoc
{
class Program
{
class NameLoc
{
public string Name;
public string Location;
}
static void Main(string[] args)
{
var lst = JsonConvert.DeserializeObject<ObservableCollection<NameLoc>>(File.ReadAllText("NameLoc.json"));
var selLst = from sel in lst where sel.Name == "A" && sel.Location == "NY" select sel;
foreach (var it in selLst)
{
Console.WriteLine($"{it.Name}, {it.Location}");
}
Console.WriteLine("Hello World!");
}
}
}
And then be sure to add a file "NameLoc.Json" Set build Action Copy To output I found errors in your json corrected below. Add Newtonsoft.json to pkgs
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
First, let's fix json format. If you are talking about collection or array then your json format should be like this:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
And you should use JArray instead of JObject to parse it so that you can apply filter.
JArray jsonArray = JArray.Parse(jsonString);
var match = jsonArray.Where(i => i["name"].ToString() == "A" && i["location"].ToString() == "NY").ToList();
You do know that your json array is incorrect, don't you? An array is within square brackets [ ... ]. I assume that is a typing error.
My advice would be to separate your concerns: split your problem into separate parts:
I have a string in JSON format containing information about Persons (?), and I need to convert this into a sequence of Persons
I have a sequence of Persons, I need only those Persons with a certain Name and Location.
If you do this, your code will be easier to understand, more reusable, easier to test and easier to change. Who does not want that?
Convert string to a sequence of Persons
For this I write an extension function. This way it looks more like LINQ. See extension methods demystified
public class Person
{
public string Name {get; set;}
public string Location {get; set;}
... // other properties
}
// Converts a JToken to a Person:
public static Person ToPerson(this JToken token)
{
// TODO: decide what to do if token == null. Return null?
Person person = token.ToObject<Person>();
return person;
}
// Converts JSON string to sequence of Persons
public static IEnumerable<Person> ToPersons(this string jsonText)
{
// TODO: exception if jsonText null, or incorrect
IEnumerable<JToken> jsonArray = JArray.Parse(jsonString);
foreach (JToken token in jsonArray)
{
Person person = token.ToPerson();
yield return person;
}
}
Usage looks like LINQ ToList():
string jsonText = ...
IEnumerable<Person> persons = jsonText.ToPersons();
Keep only Persons with certain name and location
After you've converted the json to a sequence of Persons, the rest is standard LINQ
const int name = "John Doe";
const int location = "New York City";
string jsonText = ...
var result = jsonText.ToPersons()
.Where(person => person.Name == name && person.Location == location);
Advantages of this separation of concerns
This Separation has several advantages:
You can reuse ToPersons() for other LINQ statements: "give me all elderly Persons", "give me the cities where my Persons live".
It is easier to test: just create an array of test persons as a source for your LINQ statements
It is easier to understand: people who read your LINQ statements don't have to bother anymore about the conversion: they know the conversion is used already in a lot of other places, and the test software succeeds, hence you can trust the conversion.
It is easier to change: if you want to add a PostCode to your Persons, go ahead, your LINQ statements won't change, only ToPerson()

Deserialize objects nested within an array [duplicate]

This question already has answers here:
Parse JSON in C#
(7 answers)
Closed 4 years ago.
I am struggling to deserialize the following JSON string that I am receiving from an API:
{
"appointment_types": [
{
"id": 279148,
"max_attendees": 1,
"name": "Follow up",
"billable_item": {
"links": {
"self": "https://api.cliniko.com/v1/billable_items/485545"
}
},
"practitioners": {
"links": {
"self": "https://api.cliniko.com/v1/appointment_types/279148/practitioners"
}
}
},
{
"id": 279149,
"max_attendees": 1,
"name": "Assessment",
"billable_item": {
"links": {
"self": "https://api.cliniko.com/v1/billable_items/490437"
}
},
"practitioners": {
"links": {
"self": "https://api.cliniko.com/v1/appointment_types/279149/practitioners"
}
}
}
],
"total_entries": 17,
"links": {
"self": "https://api.cliniko.com/v1/appointment_types?page=1"
}
}
I have searched but I couldn't find anything that would work for the above.
Any pointers that may get me on the right track would be greatly appreciated.
This seems to work fine for me just using dynamic...
dynamic d = JObject.Parse(json);
var totalNumber = d.total_entries.ToString();
var theId = d.appointment_types[0].id.ToString();
What have you tried?
I would create c# classes for the structure and then use Newtonsoft Json.NET for deserializion. (It is fast and already in c# but you have to add the reference.)
Here is my code:
class Program
{
static void Main(string[] args)
{
string json = File.ReadAllText("demo.json"); //Your json here
RequestResult requestResult = Newtonsoft.Json.JsonConvert.DeserializeObject<RequestResult>(json); //There is your result
Console.WriteLine("Done!");
Console.ReadLine();
}
}
class RequestResult
{
public AppointmentType[] appointment_types;
public int total_entries;
public Link links;
}
class Practitioners
{
public Link links;
}
class BillableItem
{
public Link links;
}
class Link
{
public string self;
}
class AppointmentType
{
public int id;
public int max_attendees;
public string name;
public BillableItem billable_item;
public Practitioners practitioners;
}
Then you have the result as a c# object and things like intellisense and code completion do work.

Change specific property

How do I change the CourseName from History to Gym if the ID is equally to 2?
{
"Result": {
"StudentInfo": {
"ID": 20,
"Name": "Bob",
"IgnoreThis": [
{
"ID": 123,
"Something":"abc"
}
]
},
"Courses": [
{
"ID": 1,
"CourseName":"Math"
},
{
"ID": 2,
"CourseName":"History"
}
]
}
}
This code below is just a fantasy code to show what I had in mind:
{ "Result":{ "Course" : [ if id=2 inside "Courses" then "CourseName":"Gym" ] }}
I will be using Postman.
Here is a solution that works (it could possibly be tidied up); it does however use Newtonsoft.Json (a well-known nuget package).
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string blah = "{'Result': { 'StudentInfo': { 'ID': 20, 'Name': 'Bob', 'IgnoreThis': [{'ID': 123,'Something':'abc'}]}, 'Courses': [{'ID': 1,'CourseName':'Math'},{'ID': 2,'CourseName':'History'}]}}";
dynamic json = JsonConvert.DeserializeObject(blah);
var dynamicJson = json.Result.Courses; // included to show how dynamic could be accessed instead
JObject arr = json;
foreach (var course in arr["Result"]["Courses"].Where(x => x["ID"].Value<int>() == 2))
{
course["CourseName"] = "Gym";
}
var newResult = json.ToString();
}
}
}

Search for a nested value inside of a JSON.net object in C#

I've got a JSON stream coming back from a server, and I need to search for a specific value of the node "ID" using JSON.net to parse the data.
And I can almost make it work, but not quite because the results coming back are deeply nested in each other -- this is due to the fact that I'm getting a folder structure back. I've boiled the JSON down to a much simpler version. I'm getting this:
{
"data": {
"id": 0,
"name": "",
"childFolders": [{
"id": 19002,
"name": "Locker",
"childFolders": [{
"id": 19003,
"name": "Folder1",
"childFolders": [],
"childComponents": [{
"id": 19005,
"name": "route1",
"state": "STOPPED",
"type": "ROUTE"
}]
}, {
"id": 19004,
"name": "Folder2",
"childFolders": [],
"childComponents": [{
"id": 19008,
"name": "comm1",
"state": "STOPPED",
"type": "COMMUNICATION_POINT"
}, {
"id": 19006,
"name": "route2",
"state": "STOPPED",
"type": "ROUTE"
}, {
"id": 19007,
"name": "route3",
"state": "STOPPED",
"type": "ROUTE"
}]
}],
"childComponents": []
}],
"childComponents": []
},
"error": null
}
I can almost get there by going:
var objects = JObject.Parse(results);
var subobjects = objects["data"]["childFolders"][0]["childFolders"][1];
I can see in the debug view that it'll parse the object, but won't let me search within.
My ultimate goal is to be able to search for "route3" and get back 19007, since that's the ID for that route. I've found some results, but all of them assume you know how far nested the object is. The object I'm searching for could be 2 deep or 20 deep.
My ultimate goal is to be able to search for "route3" and get back 19007
You can use linq and Descendants method of JObject to do it:
var dirs = JObject.Parse(json)
.Descendants()
.Where(x=>x is JObject)
.Where(x=>x["id"]!=null && x["name"]!=null)
.Select(x =>new { ID= (int)x["id"], Name = (string)x["name"] })
.ToList();
var id = dirs.Find(x => x.Name == "route3").ID;
You can use the SelectToken or SelectTokens functions to provide a JPath to search for your desired node. Here is an example that would provide you the route based on name:
JObject.Parse(jsonData)["data"].SelectToken("$..childComponents[?(#.name=='route3')]")
You can find more documentation on JPath here
Simply write a recursive function:
private Thing FindThing(Thing thing, string name)
{
if (thing.name == name)
return thing;
foreach (var subThing in thing.childFolders.Concat(thing.childComponents))
{
var foundSub = FindThing(subThing, name);
if (foundSub != null)
return foundSub;
}
return null;
}
class RootObject
{
public Thing data { get; set; }
}
class Thing
{
public int id { get; set; }
public string name { get; set; }
public List<Thing> childFolders { get; set; } = new List<Thing>();
public List<Thing> childComponents { get; set; } = new List<Thing>();
}
And using it:
var obj = JsonConvert.DeserializeObject<RootObject>(jsonString);
var result = FindThing(obj.data, "route3");

How to create set of Multiple JSON with same header

I have a JSON that has the following pattern to be created before
hitting the API, See below
"recipientSetInfos":
[
{
"recipientSetMemberInfos":
[
{
"fax": "",
"email": ""
}
],
"recipientSetRole":
{
"SIGNER": "enum",
"APPROVER": "enum"
},
"signingOrder": 0
}
]
Using this predefined Format, i want to create Multiple signer set's,
like the below.
"recipientSetInfos":
[
{
"recipientSetMemberInfos":
[{
"email": "def#gmail.com"
}],
"recipientSetRole": "SIGNER"
}, {
"recipientSetMemberInfos": [{
"email": "abc#gmail.com"
}],
"recipientSetRole": "SIGNER"
}],
I am using C# Programming Language, if i just hard code it & send. It
works but if i want to create dynamically. How can i achieve this.
Currently I am using this like
RecipientSetInfo rec_Info = new RecipientSetInfo();
rec_Info.recipientSetMemberInfos = List_Emails;
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
which gives an output of :
{
"recipientSetMemberInfos":
[
{"email":"abc#ae.com"},
{"email":"def#gmail.com"},
{"email":"fgh#gmail.com"}
],
"recipientSetRole":"SIGNER"
}
But using this logic, i am not getting the desired output. It is
considering all 3 emails as one.
Just to add, one more thing with the help of one user, i tried to code out this
foreach (var email in List_Emails)
{
var rec_Info = new RecipientSetInfo();
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
}
But problem still exists, since "recipientSetInfos" has two subdivisions i.e. recipientSetRole & recipientSetMemberInfos within which "recipientSetMemberInfos" has one attribute Email.
So when i add the two lists together it comes out Email to be Null
"recipientSetInfos":
[
{"recipientSetMemberInfos":null,
"recipientSetRole":"SIGNER"
‌​},
{"recipientSetMemberInfos":null,
"recipientSetRole":"SIGNER"
}
]
Structure for both the elements i have created like -
public class RecipientSetMemberInfo
{
public string email { get; set; }
}
public class RecipientSetInfo
{
public List<RecipientSetMemberInfo> recipientSetMemberInfos { get; set; }
public string recipientSetRole { get; set; }
}
Please suggest ??
Your problem is that you create one RecipientSetInfo instead of one for each email.
The following will loop through List_Emails collection and will add them to the list List_Recipients.
foreach (var email in List_Emails)
{
var rec_Info = new RecipientSetInfo();
rec_Info.recipientSetRole = "SIGNER";
List_Recipients.Add(rec_Info);
}

Categories

Resources