How do I get all the fields using json.net? - c#

A third party is giving me something similar to the below. When I know the key (such as easyField) getting the value is easy. Below I write it in the console. However the third party gave me json that uses random keys. How do I access it?
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
Console.WriteLine("{0}", j["easyField"]);
return;
}

You can use reflection with JSON.NET! It will give you the keys of your fields.
Try it online: Demo
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class Program
{
public IEnumerable<string> GetPropertyKeysForDynamic(dynamic jObject)
{
return jObject.ToObject<Dictionary<string, object>>().Keys;
}
public void Main()
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
foreach(string property in GetPropertyKeysForDynamic(j))
{
Console.WriteLine(property);
Console.WriteLine(j[property]);
}
}
}
Edit:
An even simpler solution:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public void Main()
{
var r = new Random();
dynamic j = JsonConvert.DeserializeObject(string.Format(#"{{""{0}"":""hard"", ""easyField"":""yes""}}", r.Next()));
foreach(var property in j.ToObject<Dictionary<string, object>>())
{
Console.WriteLine(property.Key + " " + property.Value);
}
}
}

This is what I had used in my project to get fields and values of a class:
public static List<KeyValuePair> ClassToList(this object o)
{
Type type = o.GetType();
List<KeyValuePair> vals = new List<KeyValuePair>();
foreach (PropertyInfo property in type.GetProperties())
{
if (!property.PropertyType.Namespace.StartsWith("System.Collections.Generic"))
{
vals.Add(new KeyValuePair(property.Name,(property.GetValue(o, null) == null ? "" : property.GetValue(o, null).ToString()))
}
}
return sb.ToString();
}
Note that the reason I was checking !property.PropertyType.Namespace.StartsWith("System.Collections.Generic") as It was causing infinte loops in entity models and if that is not the case you can remove the if condition.

Related

Can't access arguments of attribute from system library using source generator

I am trying to make a source generator for mapping columns from the google bigquery api client to class properties. I'm having trouble getting custom column names from a ColumnAttribute on the properties. ConstructorArguments is always empty and columnAttribute.AttributeClass in this sample is always an ErrorTypeSymbol. If I try to load that type using compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute") the result is always null.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace BigQueryMapping;
[Generator]
public class BigQueryMapperGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// add marker attribute
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedAttribute.g.cs", SourceText.From(Attribute, Encoding.UTF8)));
// add static interface
context.RegisterPostInitializationOutput(ctx =>
ctx.AddSource("BigQueryMappedInterface.g.cs", SourceText.From(Interface, Encoding.UTF8)));
// get classes
IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is ClassDeclarationSyntax c && c.AttributeLists.Any(),
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)
)
.Where(static m => m is not null)!;
IncrementalValueProvider<(Compilation Compilation, ImmutableArray<ClassDeclarationSyntax>Syntaxes)>
compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
context.RegisterSourceOutput(compilationAndClasses,
static (spc, source) => Execute(source.Compilation, source.Syntaxes, spc));
static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
{
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists)
{
foreach (var attributeSyntax in attributeListSyntax.Attributes)
{
var fullName = context.SemanticModel.GetTypeInfo(attributeSyntax).Type?.ToDisplayString();
if (fullName == "BigQueryMapping.BigQueryMappedAttribute")
return classDeclarationSyntax;
}
}
return null;
}
static void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> classes,
SourceProductionContext context)
{
try
{
if (classes.IsDefaultOrEmpty)
return;
var distinctClasses = classes.Distinct();
var classesToGenerate = GetTypesToGenerate(compilation, distinctClasses, context.CancellationToken);
foreach (var classToGenerate in classesToGenerate)
{
var result = GeneratePartialClass(classToGenerate);
context.AddSource($"{classToGenerate.RowClass.Name}.g.cs", SourceText.From(result, Encoding.UTF8));
}
}
catch (Exception e)
{
var descriptor = new DiagnosticDescriptor(id: "BQD001",
title: "Error creating bigquery mapper",
messageFormat: "{0} {1}",
category: "BigQueryMapperGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, null, e.Message, e.StackTrace));
}
}
}
static IEnumerable<ClassToGenerate> GetTypesToGenerate(Compilation compilation,
IEnumerable<ClassDeclarationSyntax> classes,
CancellationToken ct)
{
var columnAttributeSymbol =
compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.Schema.ColumnAttribute");
foreach (var #class in classes)
{
Debug.WriteLine($"Checking class {#class}");
ct.ThrowIfCancellationRequested();
var semanticModel = compilation.GetSemanticModel(#class.SyntaxTree);
if (semanticModel.GetDeclaredSymbol(#class) is not INamedTypeSymbol classSymbol)
continue;
var info = new ClassToGenerate(classSymbol, new());
foreach (var member in classSymbol.GetMembers())
{
if (member is IPropertySymbol propertySymbol)
{
if (propertySymbol.DeclaredAccessibility == Accessibility.Public)
{
if (propertySymbol.SetMethod is not null)
{
var columnName = propertySymbol.Name;
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
if (columnAttribute is not null)
{
if (!columnAttribute.ConstructorArguments.IsDefaultOrEmpty)
{
var nameArg = columnAttribute.ConstructorArguments.First();
if (nameArg.Value is string name)
{
columnName = name;
}
}
}
info.Properties.Add((columnName, propertySymbol));
}
}
}
}
yield return info;
}
}
static string GeneratePartialClass(ClassToGenerate c)
{
var sb = new StringBuilder();
sb.Append($#"// <auto-generated/>
namespace {c.RowClass.ContainingNamespace.ToDisplayString()}
{{
public partial class {c.RowClass.Name} : BigQueryMapping.IBigQueryGenerated<{c.RowClass.Name}>
{{
public static {c.RowClass.Name} FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row)
{{
return new {c.RowClass.Name}
{{");
foreach (var (columnName, property) in c.Properties)
{
// would like to check if key exists but don't see any sort of ContainsKey implemented on BigQueryRow
var tempName = $"___{property.Name}";
var basePropertyType = property.Type.WithNullableAnnotation(NullableAnnotation.None).ToDisplayString();
if (basePropertyType.EndsWith("?"))
{
basePropertyType = basePropertyType.Substring(default, basePropertyType.Length - 1);
}
sb.Append($#"
{property.Name} = row[""{columnName}""] is {basePropertyType} {tempName} ? {tempName} : default,");
}
sb.Append($#"
}};
}}
}}
}}");
return sb.ToString();
}
private record struct ClassToGenerate(INamedTypeSymbol RowClass,
List<(string ColumnName, IPropertySymbol Property)> Properties);
public const string Attribute = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
[System.AttributeUsage(System.AttributeTargets.Class)]
public class BigQueryMappedAttribute : System.Attribute
{
}
}";
public const string Interface = /* lang=csharp */ #"// <auto-generated/>
namespace BigQueryMapping {
public interface IBigQueryGenerated<TRow> {
static TRow FromBigQueryRow(Google.Cloud.BigQuery.V2.BigQueryRow row) => throw new System.NotImplementedException();
}
}";
}
I have tried this with both System.ComponentModel.DataAnnotations.Schema.ColumnAttribute and a custom attribute injected via context.RegisterPostInitializationOutput to similar results. I have also tried rewriting this to use ISourceGenerator instead of IIncrementalGenerator and gotten the same behavior. Am wondering what I need to do to get columnAttribute loading correctly.
Thanks for any help in advance
It's hard to psychic debug the code but this does jump out:
var columnAttribute = propertySymbol.GetAttributes().FirstOrDefault(a =>
a.AttributeClass!.ToDisplayString() == "Column");
I'd guess the class here would be the fully qualified name. You already fetched the ColumnAttribute type earlier, so what'd be even better is to compare the AttributeClass to that type rather than doing string checks like this.
As a semi-related comment, if you're looking for types/members that are annotated with a specific attribute, rather than doing it yourself we have SyntaxValueProvider.ForAttributeWithMetadataName which is pretty heavily optimized to reduce the performance impact on your Visual Studio. It requires 17.3 or higher, but as long as you're OK with that it'll generally help performance.

How to pass parameters to an api in c#?

I have below api url
http://myapi/api/getproduct?
These parameters will get created from below class
public class ApiParamters
{
public string id {get;set;}
public string name {get;set;}
public List<string> otherNames {get;set;}
}
If value of above parameters are
id=1
name="product1"
OtherNames="oldProduct1" and "oldProduct2"
Then api url should be as below
http://myapi/api/getproduct?id=1&name=product1&OtherNames=oldProduct1&OtherNames=oldProduct2
How to dynamically create these kind of url (GENERIC SOLUTION IS REQUREID because I have to implement simliar logic for other APIs too)
Here's what I came up with using reflection. I'm sure it doesn't handle all the cases, but see if it works for you.
using System.Web;
using System.Reflection;
using System.Collections.Generic;
class Program {
public static string toQueryString(string url, object o)
{
var query = HttpUtility.ParseQueryString(string.Empty);
FieldInfo[] myField = o.GetType().GetFields();
for (int i = 0; i < myField.Length; i++)
{
// Is it a list?
var t = myField[i].GetValue(o) as System.Collections.IEnumerable;
if (t != null)
{
dynamic lst = myField[i].GetValue(o);
int index = 1;
foreach (dynamic oo in lst)
{
query[myField[i].Name + index++] = oo.ToString();
}
}
else
{
query[myField[i].Name] = myField[i].GetValue(o).ToString();
}
}
return query.Count == 0 ? url : url + "?" + query.ToString();
}
}

C# How to add string to a list from a different file

I'm new to C# and OOP I have two different files. File A where I've created the list.
//This file will contain predetermine list of responses.
using System.Linq;
using System.Collections.Generic;
public class Responses
{
//bot name
static string nomber = "Jarvis";
List<string> answer = new List<string>(){
$"Mi nomber ta {nomber}",
"Mi ta bon"
};
public void AddToList(string value){
this.answer.Add(value);
}
public string Answer(int id)
{
return answer.ElementAt(id);
}
}
And in file B I have these two lines of code to add the string 1 to the list, I've also included the Generics and Linq System in file B.
var response = new Responses();
response.answer.Add("1");
I've tried creating a method called AddToList to pass the value and add it to the list, but with no luck. When I try to display the list at index 2 I'll get an argument out of range instead of the value "1".
*Also both files are located in the same folder.
After read your source code, I understand your problem. First you Add new element to Response.answers of Interperter and it return id then you get Response.answer of Output by that id. Of course you never get that, because they were 2 different instances.
I provide 2 options for you:
Option 1: Make Reponses single instance (singleton)
Responses.cs
using System.Linq;
using System.Collections.Generic;
public class Responses
{
private static Responses _instance = new Responses();
public static GetInstance() {
return _instance;
}
//bot name
static string nomber = "Jarvis";
List<string> answer = new List<string>(){
$"Mi nomber ta {nomber}",
"Mi ta bon"
};
public void AddToList(string value){
this.answer.Add(value);
}
public string Answer(int id)
{
return answer.ElementAt(id);
}
}
Then change on other files
//from
var responses = new Responses();
//to
var responses = Responses.GetInstance();
//from
responses.answer.Add()
//to
reponses.AddToList()
Option 2: Make Responses static
Response.cs
using System.Linq;
using System.Collections.Generic;
public static class Responses
{
//bot name
static string nomber = "Jarvis";
static List<string> answer = new List<string>(){
$"Mi nomber ta {nomber}",
"Mi ta bon"
};
public static void AddToList(string value){
this.answer.Add(value);
}
public static string Answer(int id)
{
return answer.ElementAt(id);
}
}
Output.cs
using System;
public class Output
{
public void Return(int respondType, int respond)
{
switch(respondType)
{
case 0:
Console.WriteLine(Responses.Answer(respond));
break;
default:
Console.WriteLine("Mi no ta kompronde");
break;
}
}
}
Interperter.cs
using System.Collections.Generic;
using System.Linq;
public class Interpreter
{
public int UserInputType(string value)
{
// Turns the user input into an array of words
string[] words = value.Split(' ');
int returnValue = 2;
//int match = 0;
Responses.AddToList("1");
//This stores the correct response to the given question
//var element = new List<int>();
foreach(var word in words)
{
// if(!string.IsNullOrWhiteSpace(word))
// {
foreach(var listOfQuestions in userInputedQuestions)
{
//Convert words in the listOfQuestions to array string to match them with the userInputedQuestion
string[] listOfQWords = listOfQuestions.Split(" ");
//Check how many words matches the predefined list of questions
foreach(var qWord in listOfQWords){
if(word == qWord){
returnValue = 0;
}
}
}
}
// }
return returnValue;
}
private List<string> userInputedQuestions = new List<string>(){
"Ki ta bo nomber?",
"Konta ku bo?"
};
}
Hope it helps

Converting dot notation strings to multidimensional arrays

Does anyone know of a C# equivalent of this occassional PHP task:
Convert dot syntax like "this.that.other" to multi-dimensional array in PHP
That is, to convert a list of strings like level1.level2.level3 = item into a dictionary or multidimensional array?
I'd assume the dictionary would need to hold items of type object and I'd later cast them to Dictionary<string, string> or a string if it's a final item.
Does code like this work?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string input = "level1.level2.level3 = item";
string pattern = "^(?'keys'[^=]+)=(?'value'.*)";
Match match = Regex.Match(input, pattern);
string value = match.Groups["value"].Value.Trim();
string[] keys = match.Groups["keys"].Value.Trim().Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>();
foreach (string key in keys)
{
if (dict.ContainsKey("key"))
{
dict[key].Add(value);
}
else
{
dict.Add(key, new List<string>() { value });
}
}
}
}
}
I guess you could do it like this, though (i'm sure more optimization could be done)
using System;
using System.Collections.Generic;
public class Program
{
public class IndexedTree {
private readonly IDictionary<string, IndexedTree> _me;
private object _value;
private readonly string _splitKey = ".";
public IndexedTree this[string key] {
get {
return _me[key];
}
}
public object Value { get; set; }
public void Add(string dottedItem) {
if ( string.IsNullOrWhiteSpace( dottedItem ) ) {
throw new ArgumentException("dottedItem cannot be empty");
}
int index;
if ( (index = dottedItem.IndexOf( _splitKey ) ) < 0 ) {
throw new ArgumentException("dottedItem didn't contain " + _splitKey);
}
string key = dottedItem.Substring(0, index), rest = dottedItem.Substring(index + 1);
IndexedTree child;
if (_me.ContainsKey(key)) {
child = _me[key];
} else {
child = new IndexedTree( _splitKey );
_me.Add(key, child);
}
if (rest.IndexOf(_splitKey) >= 0) {
child.Add(rest);
} else {
// maybe it can be checked if there is already a value set here or not
// in case there is a warning or error might be more appropriate
child.Value = rest;
}
}
public IndexedTree(string splitKey) {
_splitKey = splitKey;
_me = new Dictionary<string, IndexedTree>();
}
}
public static void Main()
{
IndexedTree tree = new IndexedTree(".");
tree.Add("Level1.Level2.Level3.Item");
tree.Add("Level1.Level2.Value");
Console.WriteLine(tree["Level1"]["Level2"].Value);
Console.WriteLine(tree["Level1"]["Level2"]["Level3"].Value);
}
}
You could see the result here:
https://dotnetfiddle.net/EGagoz

How to write extension methods for anonymous types?

I'm trying to create a CSV extension method for my enumerable list and I'm stumped. Here's how I created my simple enumerated list:
var CAquery = from temp in CAtemp
join casect in CAdb.sectors
on temp.sector_code equals casect.sector_code
select new
{
CUSIP = temp.equity_cusip,
CompName = temp.company_name,
Exchange = temp.primary_exchange
};
CAquery.WriteToCSVFile();
This is what I have done so far in creating an extension method (which I think is wrong):
public static class CSVExtensions
{
public static void WriteToCSVFile(this IEnumerable<T> myList)
{
Do you see what I'm doing wrong?
You have to specify the generic type parameter in the method signature:
public static class CSVExtensions
{
public static void WriteToCSVFile<T>(this IEnumerable<T> myList)
{
//your code here
}
}
Are you truly trying to write an extension method that should work on any IEnumerable<T> or is your type more specific? If the later is the case you should replace T with the type you want to support (or add sufficient constraints).
Edit:
In light of comments - you should project to a class instead of an anonymous type in your query - then you can use an extension method for this particular type, i.e.:
class CompanyTicker
{
public string CUSIP {get;set;}
public string CompName {get;set;}
public string Exchange {get;set;}
}
Now your query can be:
var CAquery = from temp in CAtemp
join casect in CAdb.sectors
on temp.sector_code equals casect.sector_code
select new CompanyTicker
{
CUSIP = temp.equity_cusip,
CompName = temp.company_name,
Exchange = temp.primary_exchange
};
And your extension method (which now doesn't need to be generic) becomes:
public static class CSVExtensions
{
public static void WriteToCSVFile(this IEnumerable<CompanyTicker> myList)
{
//your code here
}
}
It is possible to do what you are trying to do using reflection. The performance is going to be somewhat worse than if you write non-generic code however.
Here is a complete code sample:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var seq =
Enumerable.Range(0, 100)
.Select(i => new { Name = "Item" + i, Value = i })
;
seq.WriteCsv(Console.Out);
Console.ReadLine();
}
}
public static class CsvExtension
{
public static void WriteCsv<T>(this IEnumerable<T> seq, TextWriter writer)
{
var type = typeof(T);
MethodInfo[] getters = type.GetProperties().Select(pi => pi.GetGetMethod()).ToArray();
// only supporting simple properties
// indexer properties will probably fail
var args = new object[0];
foreach (var item in seq)
{
for (int i = 0; i < getters.Length; i++)
{
if (i != 0)
writer.Write(",");
Object value = getters[i].Invoke(item, args);
var str = value.ToString();
if (str.Contains(",") || str.Contains("\""))
{
var escaped = str.Replace("\"", "\\\"");
writer.Write("\"");
writer.Write(escaped);
writer.Write("\"");
}
else
{
writer.Write(str);
}
}
writer.WriteLine();
}
}
}

Categories