Summary
MANASYS Jazz generates CICS (= mainframe) Web services as COBOL programs that communicate using JSON, and it also generates related C# interfaces that expose these services to client programs as properties and methods. Program JSPG2 responds with a single employee record, JSPG2A is a version that can return up to 10 Employee records. JSPG2 works perfectly (see this video), but JSPG2A does not.
Detail
To map hierarchical COBOL record structures to C# classes, a set of nested C# classes is defined like this
namespace MyJSv
{
public class ResponseJSPG2A
{
public class JSPG2AResponse_
{
public class OJSPG2A_
{
//...
public int JZ_Employee_BrowseCount { get; set; }
// and more scalar classes,
public class JZ_Employee_
{
public string JZ_Employee_NthReturnCode { get; set; }
public string EMPNO { get; set; }
// and more classes within the Employee record
}
public JZ_Employee_[] JZ_Employee { get; } = new JZ_Employee_[10];
}
public OJSPG2A_ OJSPG2A { get; } = new OJSPG2A_ ();
}
public JSPG2AResponse_ JSPG2AResponse { get; } = new JSPG2AResponse_ ();
}
}
For a single occurrence of JZ-Employee its definition is
public JZ_Employee_ JZ_Employee { get; } = new JZ_Employee_ ();
Otherwise the definition of ResponseJSPG2 and ResponseJSPG2A are the same
Newtonsoft.JSON converts the incoming JSON to C# with
Response = JsonConvert.DeserializeObject<ResponseJSPG2>(result);
and then
AssignResponseToProperties(true);
assigns data from ResponseJSPG2 to the properties that JSPG2Client wishes to expose.
This all works perfectly with ResponseJSPG2, where there is a single Employee record. The equivalent with <ResponseJSPG2A> where there is an array of 10 records returns 10 null records, and the equivalent AssignResponseToProperties fails when it attempts to reference to an Employee field: -
_EMPNO = Response.JSPG2AResponse.OJSPG2A.JZ_Employee[EmployeeSub].EMPNO;
Yet Visual Studio debugging, and the same test of the web service program JSPG2A with test utility ReadyAPI, shows that JSON is returned with 10 Employee records, all containing the expected data.
Here is the JSON returned to the test, edited to remove most fields and only 2 Employee records, so that it matches the nested C# classes above: -
{
"JSPG2AResponse": {
"OJSPG2A": {
"JZ_Employee_BrowseCount": 16,
"JZ_Employee": [
{
"JZ_Employee_NthReturnCode": "F",
"EMPNO": "000060"
},
{
"JZ_Employee_NthReturnCode": "N",
"EMPNO": "000090"
}
]
}
}
}
Question
Is there a way of getting DeserializeObject to handle array classes automatically, or do I have to use Newtonsoft.Json.Linq and write logic to parse the JSON and handle the array explicitly? Or some other way of achieving my objective?
Your problem is that the JZ_Employee property is an array-valued property that lacks a setter:
public JZ_Employee_[] JZ_Employee { get; } = new JZ_Employee_[10];
Because .Net arrays cannot be resized, Json.NET treats them as read-only collections whose contents need to be accumulated into a temporary list which is then used to construct the array and set it back in its parent. However, your array property has no setter, so Json.NET sees it as completely read-only and skips it entirely.
To resolve this problem, you could add a setter. It could be protected or private if you mark the property with [JsonProperty]:
[JsonProperty]
public JZ_Employee_[] JZ_Employee { get; private set; } = new JZ_Employee_[10];
Demo fiddle #1 here.
Alternatively, you could leave the property as get-only but replace the array with a resizable collection such as List<JZ_Employee_>:
public List<JZ_Employee_> JZ_Employee { get; } = new List<JZ_Employee_>();
Demo fiddle #2 here.
This second option has the advantage that you can apply [JsonConverter(typeof(SingleOrArrayConverter<JZ_Employee_>))] or just [JsonConverter(typeof(SingleOrArrayListConverter))] from the answers to How to handle both a single item and an array for the same property using JSON.net to JZ_Employee, which will allow you to merge the ResponseJSPG2 and ResponseJSPG2A classes:
[JsonConverter(typeof(SingleOrArrayConverter<JZ_Employee_>))]
public List<JZ_Employee_> JZ_Employee { get; } = new List<JZ_Employee_>();
The converter will automatically convert the case of a single JZ_Employee object into a list with one item.
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 want to create a method that displays the information contained in an object, that will work dynamically, with any object. I'm having trouble handling properties that are other custom classes. In the example below the Person has Phones and Occupations which both are other classes. When the data is displayed, the value on the screen currently is:
TestReflection.Person
Name: Mary
Phones: TestReflection.Phones
Occupations: TestReflection.Occupations
It just displays the name of class, like TestReflection.Phones, rather than the data inside that object.
How can I change this code to show information like this instead?
TestReflection.Person
Name: Mary
Phones:
TestReflection.Phones
Type: 1
Number: 555XYZ
Occupations:
TestReflection.Occupations
Type: 5
Description: Secretary
Here is my code:
class Program
{
static void Main(string[] args)
{
List<Person> listPeson = new List<Person>();
var person1 = new Person();
person1.Name = "Mary";
person1.Phones = new Phones { new Phone { Type = 1, Number = "555XYZ" } };
person1.Occupations = new Occupations {new Occupation { Type = 5, Description = "Secretary" }};
listPeson.Add(person1);
DynamicExport(listPeson);
Console.ReadLine();
}
public static void DynamicExport<T>(List<T> listReg)
{
for (int i = 0; i < listReg.Count; i++)
{
Console.WriteLine(listReg[i].GetType());
foreach (var item in listReg[i].GetType().GetProperties())
{
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
}
}
}
}
class Person
{
public string Name { get; set; }
public Phones Phones { get; set; }
public Occupations Occupations { get; set; }
}
class Phones : List<Phone> { }
class Phone
{
public int Type { get; set; }
public string Number { get; set; }
}
class Occupations : List<Occupation> { }
class Occupation
{
public int Type { get; set; }
public string Description { get; set; }
}
I made some edits to your question - I hope I understood you correctly.
If you want to export data
If your question is really about displaying data, then there are better ways to do it than creating your own export method. The format you are trying to display looks similar to YAML. There's also JSON and XML. Using one of these libraries is probably better than writing your own method:
YamlDotNet NuGet package
Json.NET NuGet Package
System.Xml.Serialization.XmlSerializer class
If you want to learn more about reflection
Maybe you're interested in learning more about reflection, and the export is just an example to play around with it. In that case, let's look at this line:
Console.WriteLine($"{item.Name}: {item.GetValue(listReg[i], null)}");
$"{item.GetValue(listReg[i], null)}" ends up calling person1.Phones.ToString(). The default behavior of ToString just displays the type name. You could override that behavior, like this:
class Phones : List<Phone>
{
public override string ToString()
{
return Program.DynamicExportToString(this);
// ... where DynamicExportToString is a modified version of DynamicExport that
// builds and returns a string rather than sending it directly to the Console.
}
}
Maybe you want to be able to handle any class, even when you cannot override ToString in all of the classes you might export. Then you will need to put some additional logic in the DynamicExport method, because...
$"{item.Name}: {item.GetValue(listReg[i], null)}"
... doesn't work for every situation. We need to display different things depending on the type of the property.
Consider how you want to handle null values. Maybe something like $"{item.Name}: <null>"
Use your existing $"..." code if the type is...
a primitive type.
DateTime
String
... or a Nullable<> of one of those types.
If the type implements IEnumerable, loop over the contents of the collection and recursively call your export code for each element.
It's important to check for this interface after you've checked if the type is a String, because String implements IEnumerable.
Otherwise, recursively call your export code on this value.
When you call your export code recursively, it would be wise to guard against infinite loops. If the object you're trying to export contains a circular reference - you could quickly wind up with a StackOverflowException. To avoid this, maintain a stack of objects that have already been visited.
I think the above advice is generally applicable whenever you're using reflection to traverse an object graph - whether it's for serialization or any other purpose.
I hope this helps!
I am integrating with a courier that requires me to pass box dimensions for each box in my consignment to their API in JSON format. I am able to set individual properties like RecipientName, but am not sure how to pass the box details for the varying number of boxes for each consignment.
The JSON needs to look like this (example is for a 2 box consignment):
{
"RecipientName": "Joe Bloggs",
"Packages" : [{
"boxNumber": "1",
"boxHeight": 1.55,
"boxLength": 1.55,
"boxWidth": 1.55
},
{
"boxNumber": "2",
"boxHeight": 2.55,
"boxLength": 2.55,
"boxWidth": 2.55
}]
}
I have built 2 classes, one that describes the structure of the JSON, and another that contains the method to serialize the JSON.
My JSON structure class looks like this (I have used a List because I have read that arrays are a fixed length, and because the number of boxes with vary I cannot use arrays):
public class API_JSON
{
public class Rootobject
{
public string RecipientName { get; set; }
public List<Package> Packages { get; set; }
}
public class Package
{
public string boxNumber { get; set; }
public double boxHeight { get; set; }
public double boxLength { get; set; }
public double boxWidth { get; set; }
}
}
And my API methods class looks like this:
public class API_Methods
{
public string recipientName;
public List<string> boxnumber;
public List<double> boxHeight;
public List<double> boxLength;
public List<double> boxWidth;
public Boolean SubmitConsignment(out string JSONData)
{
var NewRequestObject = new API_JSON.RootObject
{
Recipient = recipientName,
Packages = new API_JSON.Package
{
foreach (string item in ContainerNumber)
{
boxNumber=???,
boxHeight=???,
boxLength???=,
boxWidth=???
}
}
}
string JSONData = JsonConvert.SerializeObject(NewRequestObject);
return true;
}
}
I am then instantiating the object, setting its public variables, then running the method list this:
API_Methods myObject = new API_Methods();
myObject.recipientName;
myObject.boxnumber.Add(1);
myObject.boxnumber.Add(2);
myObject.boxHeight.Add(1.55);
myObject.boxHeight.Add(2.55);
myObject.boxLength.Add(1.55);
myObject.boxLength.Add(2.55);
myObject.boxWidth.Add(1.55);
myObject.boxWidth.Add(2.55);
bool test = API_Methods.SubmitConsignment(out JSON);
My problem is with the foreach loop - I know the code is incomplete - but I was hoping to iterate through the lists, but even with an empty foreach loop it appears to be the wrong place to put the loop as I start getting syntax errors about an expected "}"
You're actually overcomplicating this for yourself - create complete package objects, and add them to the List Packages, and then pass the rootobject to the serializer.
The error you are getting is because you are not correctly initializing / filling your Packages List. Your object is invalid, hence the serializer is throwing exceptions.
This will be a lot easier for you if you create some constructors for your objects, something like this:
public Package(number, height, length, width)
{
boxNumber = number;
boxHeight = height;
//rest of your properties here in same format
}
You can then also make your setters private in the class, if you wish.
You can then easily create your package objects:
var package1 = new Package(10, 10, 10, 10);
This should make it a lot easier to create your list of boxes to put in your rootObject.
You can add each package to the packages list (individually or within a foreach loop):
Packages.Add(package1)
Or you could even start getting more concise:
Packages.Add(new Package(10,10,10,10));
You want to separate your concerns more to help keep this clear - so I'd recommend you fully construct your rootObject, add the packages to the list in one class (your 3rd code snippet), and then serialize it another (your 2nd code snippet).
Edit:
I think you'd find it easier to refactor your code somewhat:
1) Have a public rootobject in your Json_Api class, with get; set;. Get rid of the box collections. Get rid of your foreach loop from here too.
public class API_Methods
{
public rootObject RootObject { get; set; }
public Boolean SubmitConsignment(out string JSONData)
{
string JSONData = JsonConvert.SerializeObject(NewRequestObject);
return true;
}
}
2) Set the properties of this rootobject outside this class (where you currently initialize your objects). Add the New Package()s to Packages list here too.
API_Methods myObject = new API_Methods();
myObject.RootObject.recipientName = "NAME";
myObject.RootObject.Packages.Add(new Package(10,10,10,10);
myObject.RootObject.Packages.Add(new Package(20,20,20,20);
bool test = API_Methods.SubmitConsignment(out JSON);
3) Call the API method next, it should return a serialized version of the wholerootobject, including your packages.
Just a side note, it would be more conventional to send the RootObject as a parameter to the API, and return the Json string object back.
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 have a mongo model like this:
class ObjectA {
[BsonId(IdGenerator = typeof(BsonObjectIdGenerator))]
public BsonObjectId Id;
[BsonElement("number")]
public int Number { get; set; }
[BsonElement("b")]
public List<ObjectB> objectB { get; set; }
}
class ObjectB {
[BsonElement("someProperty")]
public string SomeProperty { get; set; }
}
My problem is when I aggregate the collection with {$unwind:objectB}. The result documencts have a unique object on the property objectB (not a list).
So the cast failes with the exception:
An error occurred while deserializing the ObjectB property of class
ObjectA: Expected element name to be '_t', not
'number'.
Do I have to create a new model for this or is there a easier way to solve it?
You could also choose to work with BsonDocument directly (but that is not strongly typed and more cumbersome to work with), e.g. (I'm using the simple Posts/Tags example here)
var aggregationResults = db.GetCollection("Posts").Aggregate().ResultDocuments;
foreach (var document in aggregationResults)
{
var tag = document.GetValue("Tags").AsString;
}
Unlike the normal query and projection operators, the aggregation framework may change the structure of your document. As you already pointed out, $unwind transforms a document that contains an array into a number of documents that each have a single value of the same name.
Another approach this is to indeed create a new type for this, so
class Post {
public List<string> Tags { get; set; }
...
would become
class PostAggregationResult {
public string Tags { get; set; }
...
That is very easy to work with, but if you have very various aggregation queries, you need a large number of classes which can be annoying.