I have a class called InstrumentConfigValues with properties that has type implementing an interface. Now I have an enum by name InstrumentConfig which has set of values. These values are like keys inside the json file. I want to map something like [JsonProperty(InstrumentConfig.LowDiskpace.ToString()].
For some reason its not allowing this and complains saying:
An attribute argument must be constant expression
I referred to many post specifically JsonStringEnumConverter. But how can I map each property with the enum key. I also saw this post JsonSerializationSettings but not able to correlate to my problem. Please help/
public class InstrumentConfigValues : IInstrumentConfig
{
public double SpaceNeededForSingleRun
{
get; set;
}
public int NumberOfInputSlots
{
get; set;
}
public int SupportedChannelCount
{
get; set;
}
}
//I want this inheritance as some other class wants to access the values.
public abstract class InstrumentConfigReadWrite : InstrumentConfigValues
{
protected ReturnCodes PopulateValuesFromJObject(JObject jObject, string path)
{
try
{
if (JsonConvert.DeserializeObject<InstrumentConfigValues>(jObject.ToString()) == null)
{
return ReturnCodes.ErrorReadingFile;
}
}
catch (JsonSerializationException jex)
{
SystemDebugLogLogger.LogException(jex, "Invalid Instrument Config File Values. Data needs to be copied over.");
return ReturnCodes.ErrorReadingFile;
}
return ReturnCodes.Success;
}
}
As long as you're using a current compiler, you can use nameof.
[JsonProperty(nameof(InstrumentConfig.LowDiskpace))]
If you try using this, and get an error like Compilation error: The name 'nameof' does not exist in the current context, that means you're not using a current compiler. The nameof keyword was introduced in C# 6.0/Visual Studio 2015--anything newer than that should be fine.
Related
I have this JSON response from an API:
{
"arguments": {
"Configuration": {
"Building_Configuration.Parameters_SP.fixtureStrategy_SP": "ETA",
"Building_Configuration.Parameters_SP.dimensionSelection_SP": "Imperial",
"Building_Configuration.Parameters_SP.controllerRobotic_SP": false,
"Building_Configuration.Parameters_SP.controllerBACNet_SP": false
}
}
}
I have this Root.cs Model file that I used the JSON to C# Converter to make that corresponds to the JSON above in my solution in C# Visual Studio 2019:
public class Root
{
public Arguments arguments { get; set; }
}
public class Arguments
{
public Configuration Configuration { get; set; }
}
public class Configuration
{
[JsonProperty("Building_Configuration.Parameters_SP.fixtureStrategy_SP")]
public string BuildingConfigurationParametersSPFixtureStrategySP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.dimensionSelection_SP")]
public string BuildingConfigurationParametersSPDimensionSelectionSP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.controllerRobotic_SP")]
public bool BuildingConfigurationParametersSPControllerRoboticSP { get; set; }
[JsonProperty("Building_Configuration.Parameters_SP.controllerBACNet_SP")]
public bool BuildingConfigurationParametersSPControllerBACNetSP { get; set; }
}
I'm trying to access and return the value of BuildingConfigurationParametersSPFixtureStrategySP (the first property in the Configuration class above) in this manner:
public string CreateExecPost()
{
/*...everthing here works fine down through the end of this method. I can set
breakpoints and step through the following code and look at the values of all the
following variables and all is well
....*/
var payload = new StringContent(newPost, Encoding.UTF8, "application/json");
var result = client.PostAsync(endpoint, payload).Result.Content.ReadAsStringAsync().Result;
Root MyObject = JsonConvert.DeserializeObject<Root>(result);
return MyObject.arguments.configuration.BuildingConfigurationParametersSPFixtureStrategySP;
} //The correct value for BuildingConfigurationParametersSPFixtureStrategySP is returned and all is well!
So, the question is why does the converter generate an attribute like...
[JsonProperty("Building_Configuration.Parameters_SP.fixtureStrategy_SP")]
...above each of the { get; set; } statements? I've researched and read about JsonProperty and JsonPropertyAttribute, but I'm still not fully clear on it. I'm not seeing what the tool uses to signal it to generate the attribute or why it does it.
This tool generates code with the Json.net library by default, and the official documentation doesn’t explain much on this class: https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_Serialization_JsonProperty.htm
There are other similar questions on how to use this, for example:
What [JsonProperty] used for in c#?
The general usage of this class is when you want to, or need to, rename a property. And the last one is relevant here.
The json parser normally tries to match the property names 1:1 with your class properties.
But whenever the json property name contains reserved keywords, language syntax, or otherwise illegal property names, you need to rename it.
In your example the name Building_Configuration.Parameters_SP.fixtureStrategy_SP contains periods. If you would try to name your get;set property like this, the code would not compile.
The site that generates the code knows this, and will add the required JsonProperty to map the full json property name to the class field that was renamed to BuildingConfigurationParametersSPFixtureStrategySP (the illegal characters were removed)
See for valid property names: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names
And for reference: Accessing properties with a dot in their name
I'm trying to create a collection (list<> or IEnumerable<>) of a custom objet "InventorAttribue" that has 2 properties; Name and Value.
The "Value" property can be of various type so I thought of coding this object like this:
public class InventorAttribute<T> {
public InventorAttribute (string name, T value) {
Name = name;
Value = value;
}
public string Name { get; set; }
public T Value { get; set; }
}
Further I plan to use an "AttiributeSet" class to represent the final Autodesk Inventor AttributeSet to be stored in an Inventor's object. Here is the class and where my question stands, because of course, this code does not work as the type 'T' cannot be found (!)
public class AttributeSet
{
public AttributeSet(string category, string name {
Name = name;
Attributes = new List<InventorAttribute<T>>();
}
public string Category { get; set; }
public string Name { get; set; }
public List<InventorAttribute<T>> Attributes { get; set; }
public void AddAttribute(string name, T value){
Attributes.Add(new InventorAttribute<T>(name,value));
}
}
Question:
How can I manage to write this code, and being able to pass the "InventorAttribute.Value" type only at run time through the "AddAttribute" method.
Thanks in advance for greatly appreciated help.
Your AttributeSet class should be also parametrized:
public class AttributeSet<T>
NOTE: you cannot store InventorAttribute<T> parametrized with different T types in Attributes collection. Even if you could do that, how would you consume such collection? You will need to cast Value for each attribute to appropriate type. You will not have any benefits of having generic class here. So create non-generic InventorAttribute which will store values in property of object type.
You're probably imagining some form of inheritance. It doesn't exist here.
An InventorAttribute<string> is not a subclass of InventorAttribute<T>. Nor is it a subclass of InventorAttribute<object> (I mention this since it's usually people's next attempt to define the collection's item type). Each constructed generic type is effectively independent1.
If applicable, you may be able to introduce a new base class:
public abstract class InventorAttribute {
public string Name { get; set; }
public InventorAttribute (string name) {
Name = name;
}
}
public class InventorAttribute<T> : InventorAttribute {
public InventorAttribute (string name, T value) : base(name) {
Value = value;
}
public T Value { get; set; }
}
And you can now declare your collection to be of non-generic type InventorAttribute. But now you cannot access the Values until you cast to the more specific type.
1So far as the type system is concerned. As an implementation detail, the system is able to cleverly JIT only a single version of each method body that is applicable for all reference types. But that doesn't have any visible impact in the type system.
I need to read a CSV file with FileHelpers based on type, automatically generated by my MVC model. The model looks like this:
public partial class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
The last field is obviously generated by a foreign key in database, referring to table MerchantCategories.
Then I attempt to create an instance of FileHelperEngine with this type:
var engine = new FileHelperEngine<Merchant>();
And get the following exception:
The field: 'k__BackingField' has the type: MerchantCategory that is not a system type, so this field need a CustomConverter ( Please Check the docs for more Info).
Actually I don't need this field at all for my import, so I tried to ignore it in derived class:
[DelimitedRecord(",")]
public class MerchantForImport : Merchant {
[FieldHidden]
new public MerchantCategory MerchantCategory;
}
var engine = new FileHelperEngine<MerchantForImport>();
And still the same error. I don't need this field at all, I don't want to implement any FieldConverter for it, I never asked for this k__BackingField and it's nowhere to be found in my code!
I can't call FileHelperEngine.Options.RemoveField() because the exception is thrown by the constructor.
Where does that come from? How do I get rid of it?
From a design perspective, I think you are going about it the wrong way. You are trying to use the Merchant class for two incompatible uses. Instead you should have two separate classes.
FileHelpers is a library for describing csv files so that you can import them easily. You should have a MerchantFileSpec for describing your file. It's really not a proper C# class - it may have: dummy fields to represent unused columns; lots of attributes [FieldNullValue], [FieldQuoted], [FieldConverter]; etc. It works best with public fields (a FileHelpers limitation which is not C# best practice), etc. It is a convenience syntax for describing the import file. It should not include any business logic or special constructors, or backing fields. Keep it as simple as possible.
Then you can have your MVC-generated Merchant class which is separate. Its purpose is to describe the merchant as required by the MVC framework, with foreign keys, ids, whatever.
Then you use a FileHelperEngine<MerchantFileSpec> to read the records into an array and map it to an enumerable of Merchant (via Linq or a library like AutoMapper).
Something like:
/// Your MVC-generated class. Add methods, getters, setters, whatever.
/// FileHelpers doesn't use this class.
class Merchant
{
public long Id { get; set; }
public string Name { get; set; }
public Nullable<int> Category { get; set; }
public virtual MerchantCategory MerchantCategory { get; set; }
}
/// This is the class FileHelpers will use
/// This class describes the CSV file only. Stick to whatever
/// syntax conventions are required by FileHelpers.
[DelimitedRecord(";")]
class ProductMerchantFileSpec
{
[FieldQuoted(QuoteMode.OptionalForRead)]
public long Id;
[FieldQuoted(QuoteMode.OptionalForRead)]
public string Name;
[FieldQuoted(QuoteMode.OptionalForRead)]
// Handle non-US formats such as , decimal points
// convert from inches to centimetres?
// you get the idea...
[FieldConverter(MyCustomizedCategoryConverter)] // you get the idea
public int Category;
}
class Program
{
static void Main(string[] args)
{
var engine = new FileHelperEngine<ProductMerchantFileSpec>();
var productMerchantRecords = engine.ReadFile(filePath);
var productMerchants = productMerchantRecords
.Select(x => new Merchant() { Id = x.Id, Name = x.Name, Category = x.Category });
}
}
I received this error specifically because my object (i.e. Merchant) was missing a column that existed in the source file. I was able to work around the issue prior to realizing the missing column by adding a new property to my object class public string[] MyProperty { get; set; }. This work-around help me realize a column was missing.
i.e..
public partial class Merchant
{
public long id { get; set; }
..
..
..
public string[] MyProperty { get; set; }
}
I want to create a key value table in my database along the lines of
public class KeyValue {
public string Id { get; set; }
public dynamic Value {get; set; }
}
Using a slightly modified SqlProvider I have no problems getting CreateTable<KeyValue>() to generate varchar(1024) Id, varchar(max) Value.
I have no issues saving objects to it. The problem is when I load the objects
var content = dbConn.GetById<KeyValue>("about");
content.Value at this point is a string.
Looking at the database record, the text for value does not appear to store any type information.
Is there really anything I can do better other than manually invoking ServiceStack.Text and call deserialize with the appropriate type information?
I do not need absolute dynamic, my actual use case is for polymorphism with a base class instead of dynamic. So I don't really care what type Value is whether it's the base class, dynamic, object, etc. Regardless other than using the class
public class KeyValue {
public string Id { get; set; }
public MySpecificChildType Value {get; set; }
}
I haven't been able to get anything other than a string back for Value. Can I tell OrmLite to serialize the type information to be able to correctly deserialize my objects or do I just have to do it manually?
Edit: some further information. OrmLite is using the Jsv serializer defined by ServiceStack.Text.TypeSerializer and is in no way pluggable in the BSD version. If I add a Type property to my KeyValue class with the dynamic Value I can do
var value = content.Value as string;
MySpecificChildType strongType =
TypeSerializer.DeserializeFromString(content, content.Type);
I just really want a better way to do this, I really don't like an object of 1 type going into the db coming back out with a different type (string).
I haven't worked much with the JsvSerializer but with the JsonSerializer you can achieve this (in a few different ways) and as of ServiceStack 4.0.11 you can opt to use the JsonSerializer instead, see https://github.com/ServiceStack/ServiceStack/blob/master/release-notes.md#v4011-release-notes.
Example
public abstract class BaseClass {
//Used for second example of custom type lookup
public abstract string Type { get; set; }
}
public class ChildA : BaseClass {
//Used for second example of custom type lookup
public override string Type { get; set; }
public string PropA { get; set; }
}
And then in your init/bootstrap class you can configure the serializer to emit the type information needed for proper deserialization:
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
}
}
If you wish to use something other that the default "__type" attribute that ServiceStack uses (if you for example want to have a friendly name identifying the type rather then namespace/assembly) you can also configure your own custom type lookup as such
public class Bootstrapper {
public void Init() {
ServiceStack.Text.JsConfig.ExcludeTypeInfo = false;
ServiceStack.Text.JsConfig.IncludeTypeInfo = true;
ServiceStack.Text.JsConfig.TypeAttr = "type";
ServiceStack.Text.JsConfig.TypeFinder = type =>
{
if ("CustomTypeName".Equals(type, StringComparison.OrdinalIgnoreCase))
{
return typeof(ChildA);
}
return typeof(BaseClass);
}
}
}
I've refactored code like this:
public string CamelCASE { get; set; }
to:
public string CamelCase {get; set; }
only do discover that the input XML contains the former casing (let's call it a shouting camel). I have no control over how the XML document is produced. Nor do I burn of desire to retract my changes.
I'd like to map the loud camel property to a softly speaking one.
I've tried XmlElement and XmlMapping but to no greater success. A googling gave me only hits on how to map stuff to attributes, along lines of this post. However, I need only something like <LoudCAMEL> to be deserialized to a property public string QuietCamel.
Is there a smooth way to do so?
Edit
After adding the attribute as follows:
using System.Collections.Generic;
using System.Xml;
public class Beep : SuperBeep
{
private readonly BeepType _a;
public Beep() { _a = BeepType.SomeSome; }
public Beep(BeepType input) { _a = input; }
~Beep() { }
public override void Dispose() { }
public BeepType Aaa { get { return _a; } }
[XmlElement("CamelCASE")]
public bool CamelCase { get; set; }
}
I can see the red, wavy highlight telling me Cannot access constructor 'XmlElement' here due its protection level. When I compile, though, I get the IDE crying out loud that 'System.Xml.XmlElement' is not an attribute class.
Frankly, I'm a bit confused by the suggestion to use attributes (this is targeting .NET 2.0), since I was under the impression that attributing wasn't available to .NET prior to version 3.5. Am I mistaken?
[XmlElement("CamelCASE")]
public string CamelCase { get; set; }
should be all you need, if you are keeping the shouty name in the xml. If you want to use the quieter name in new xml, but allow the old name to still work, it gets more complicated. You could use:
public string CamelCase { get; set; }
[XmlElement("CamelCASE"), Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public string CamelCaseLegacy {
get { return CamelCase; }
set { CamelCase = value; }
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeCamelCaseLegacy() { return false; }
When serializing, the CamelCase property will serialize to <CamelCase>, and the CamelCaseLegacy element will be ignored due to the ShouldSerialize* method. However, when deserializing, the CamelCaseLegacy property will be used whenever <CamelCASE> is seen. We then map this value back to the CamelCase property.
You are referring to the wrong namespace.
Remove
using System.Xml;
and add
using System.Xml.Serialization;