I am trying to map string value fields from MongoDB to integers in VisualStudio, using .NET. The fields have one of a few known values, like "pedestrian", "bicycle", "car". How can i check against these values and map them to be represented by "0", "1", "2"? I know the basics of automapper, but i cant find how to do this.
I am getting them as a List<> and they need to stay as a List<>.
Using:
VS 19
Automapper v8
Example of a MongoDB document:
{
"Street" : "Oak",
"Object" : "pedestrian",
"Id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
"City" : "NY",
"Direction" : 0
}
Reading the rows:
public class Stuff
{
[BsonElement("_id")]
public ObjectId Id { get; set; }
public List<MyDocument> MyDocuments { get; set; }
}
...
var client = new MongoClient();
var database = client.GetDatabase("test");
var collection = database.GetCollection<Stuff>("alma");
var rows = (await collection.FindAsync(FilterDefinition<Stuff>.Empty)).ToList();
Option 1:
You don't need to use Automapper for that, the MongoDB driver converts enums as long as the name of the option matches.
[BsonNoId]
public class MyDocument
{
public string Id { get; set; }
public string Street { get; set; }
public ObjectEnum Object { get; set; }
public string City { get; set; }
public int Direction { get; set; }
}
public enum ObjectEnum
{
None = 0,
pedestrian = 1,
bicycle = 2,
car = 3
}
...
foreach (var row in rows)
{
// use the enum, or if you need the associated number just use int cast
int myNumberRepresentation = (int)row.MyDocuments[0].Object;
}
Option 2:
Write custom serializer, and your return type can be anything you like:
public enum ObjectEnum
{
None = 0,
// notice the upper case
Pedestrian = 1,
Bicycle = 2,
Car = 3
}
[BsonNoId]
public class MyDocument
{
public string Id { get; set; }
public string Street { get; set; }
[BsonSerializer(typeof(CustomObjectSerializer))]
public ObjectEnum Object { get; set; }
public string City { get; set; }
public int Direction { get; set; }
}
public class CustomObjectSerializer: SerializerBase<ObjectEnum>
{
public override ObjectEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var s = context.Reader.ReadString();
switch (s)
{
case "pedestrian":
return ObjectEnum.Pedestrian;
case "bicycle":
return ObjectEnum.Bicycle;
case "car":
return ObjectEnum.Car;
default:
return ObjectEnum.None;
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ObjectEnum value)
{
string ret = "";
switch (value)
{
case ObjectEnum.Pedestrian:
ret = "pedestrian";
break;
case ObjectEnum.Bicycle:
ret = "bicycle";
break;
case ObjectEnum.Car:
ret = "car";
break;
}
context.Writer.WriteString(ret);
}
}
Related
sorry for my bad english. But i need a little help with this method (ToObject)
I have this class
namespace Proyects
{
public class AProductType
{
public DateTime CreationDate { get; set; }
public string Product { get; set; }
}
public class AProduct
{
public AProductType A_ProductType { get; set; }
}
public class AProductPlantType
{
public string SerialNumberProfile { get; set; }
}
public class ToPlant
{
public List<AProductPlantType> A_ProductPlantType { get; set; }
}
public class AProductDescriptionType
{
public string Language { get; set; }
public string Product { get; set; }
public string ProductDescription { get; set; }
}
public class ToDescription
{
public List<AProductDescriptionType> A_ProductDescriptionType { get; set; }
}
public class Root
{
public AProduct A_Product { get; set; }
public ToPlant to_Plant { get; set; }
public ToDescription to_Description { get; set; }
}
}
I am currently using the Root one to save a Jtoken in there. But i cant save the data on the propieties from Root class.
For example:
var object= myJson.ToObject<Root>();
If i try to save data on the propiety Product from AProductType, i cant access there using using ToOject. I try someting like this
var object= myJson.ToObject<Root>().A_Product.A_ProductType.Product;
And dont work, object var become null. I need some way to save the data around this complex object saving then in the propietes from Root.
Really sorry for my english, Thanks!!!
Edit: Json file
{
"A_Product": {
"A_ProductType": {
"CreationDate": "2020-01-17T00:00:00",
"Product": "158"
}
},
"to_Plant": {
"A_ProductPlantType": [
{
"SerialNumberProfile": "E001"
}
]
},
"to_Description": {
"A_ProductDescriptionType": [
{
"Language": "EN",
"Product": "158",
"ProductDescription": "Terminal LaP (nro de serie + equipo)"
},
{
"Language": "ES",
"Product": "158",
"ProductDescription": "Terminal LaP"
}
]
}
}
Edit 2:
private static List<string> retrieveData(JObject ob, List<Root> listaObjetos)
{
List<string> ListaCodigoProducto = new List<string>();
Root objetoRot = new Root();
var A_Product = ob["A_Product"];
if (A_Product.HasValues)
{
var validacion = ob["A_Product"]["A_ProductType"];
if (validacion.Type == JTokenType.Object)
{
var objeto = validacion.ToObject<AProductType>();
ListaCodigoProducto.Add(objeto.Product);
objetoRot.A_Product.A_ProductType.Product = objeto.Product;
listaObjetos.Add(objetoRot);
}
When i try to save the product number on
objetoRot.A_Product.A_ProductType.Product
It shows NullReference exception, i cant access to the propiety in the Root object
The deserializing code is working just fine. Your problem is that you're accessing objects that aren't there.
When you say Root objetoRot = new Root();, you are creating a new, empty Root. Its A_Product value will be null. A few lines down, you are saying objetoRot.A_Product.A_ProductType.Product = objeto.Product;. But you can't get objetoRot.A_Product.A_ProductType because there is no objetoRot.A_Product to access properties on.
when im trying to desrialize to abstract class list,The Genres property in Book class stay Null, While in Journal class its get the value from my json file.
string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
using (FileStream streamFile = File.Open($"{folderPath}//products10.json", FileMode.OpenOrCreate))
{
using (StreamReader reader = new StreamReader(streamFile))
{
string fileContent = reader.ReadToEnd();
productsList = JsonConvert.DeserializeObject<List<ProductBase>>(fileContent,ProductBase.StrandartJsonConvert);
}
}
This is the JSON file:
[
{
"EditorName":"Me",
"Name":"DailyMail",
"IssueNumber":4,
"Genres":[1],
"Frequency":0,
"Id":"01c26581-3e3a-4bc2-bc97-dfbab0215f29",
"Description":"DailyMail",
"PublicationDate":"2022-01-19T12:44:32.57574+02:00",
"BasePrice":15.0,
"Type":"Journal"
},
{
"AuthorName":"Author",
"Title":"HarryPotter",
"Edition":3,
"Geners":[2,1],
"Synopsis":null,
"Id":"6674b82d-6d6d-49ac-9c92-7d84b0dd09b6",
"Description":"HarryPotter",
"PublicationDate":"2022-01-19T12:44:30.2413124+02:00",
"BasePrice":35.0,
"Type":"Book"
}
]
While in my journal class everything get in, in my book class - it isn't, it looks like the deserializtion ignores the Genres property.
public class Journal : ProductBase
{
public string EditorName { get; set; }
public string Name
{
get { return base.Description; }
set { base.Description = value; }
}
public int IssueNumber { get; set; }
public ICollection<JournalGenre> Genres { get; set; }
public JournalFrequency Frequency { get; set; }
public Journal(string editorName, string name, int issueNumber, DateTime publicationDate,
decimal basePrice, JournalFrequency frequency, params JournalGenre[] genres)
: base(name, publicationDate, basePrice)
{
this.EditorName = editorName;
this.IssueNumber = issueNumber;
this.Frequency = frequency;
this.Genres = genres.ToList();
}
}
here all the properties get the values.
public class Book : ProductBase
{
public string AuthorName { get; set; }
public string Title
{
get { return base.Description; }
set { base.Description = value; }
}
public int Edition { get; set; }
public ICollection<BookGenre> Geners { get; set; }
public string Synopsis { get; set; }
public Book(string authorName, string title, DateTime publicationDate, decimal basePrice, int edition = 1, params BookGenre[] genres)
:base(title, publicationDate, basePrice)
{
this.AuthorName = authorName;
this.Edition = edition;
this.Geners = genres.ToList();
}
}
but here the Genres stays null - the 'genres' in the const isnt get the value from the JSON file - only this prop. anything else get value.
Not sure why #JamesS deleted his answer, but he's right - the property in the JSON file is Geners for your Book, but the constructor parameter is genres.
Either correct the spelling of the property on the class and in the JSON file to Genres:
public class Book : ProductBase
{
...
public ICollection<BookGenre> Genres { get; set; }
{
"AuthorName":"Author",
"Title":"HarryPotter",
"Edition":3,
"Genres":[2,1],
"Synopsis":null,
"Id":"6674b82d-6d6d-49ac-9c92-7d84b0dd09b6",
"Description":"HarryPotter",
"PublicationDate":"2022-01-19T12:44:30.2413124+02:00",
"BasePrice":35.0,
"Type":"Book"
}
Or change the spelling of the constructor parameter to match the name used in the JSON file:
public Book(
string authorName,
string title,
DateTime publicationDate,
decimal basePrice,
int edition = 1,
params BookGenre[] geners)
I would like to map the data from one list of objects and another. I am looping through CompanyAEmployee list and able to map FullName and Title. But not able to map Children property.
public class CompanyAEmployee
{
public string FullName { get; set; }
public string Title { get; set; }
public List<CompanyAEmployee> Children { get; set; }
}
public class CompanyBEmployee
{
public string Name { get; set; }
public string PositionName { get; set; }
public List<CompanyBEmployee> Children { get; set; }
}
companyAEmployeeList; // stores all employees of companyA
var companyBEmployeeList = new List<CompanyBEmployee>();
foreach(var employee in companyAEmployeeList)
{
var companyBEmployee = new CompanyBEmployee();
companyBEmployee.Name = employee.FullName;
companyBEmployee.PositionName = employee.Title;
//how to map the children??
}
Can someone suggest a way to map Children?
You can create a recusive method, like below:
public CompanyBEmployee ComAToComB(CompanyAEmployee a){
CompanyBEmployee b = new(){
Name = a.FullName,
PositionName = a.Title,
Children = new()
};
foreach(var child in a.Children){
b.Children.Add(ComAToComB(child));
}
return b;
}
And then call it like
var comB = ComAToComB(comA);
So, in my case it was best solution to use extension methods.
You can see example here
https://dotnetfiddle.net/SwhGMY
Main idea is to use such extension method that was called recursively.
public static class ClassConverterExtensions
{
public static CompanyBEmployee ToCompanyBEmployee(this CompanyAEmployee that)
{
var result = new CompanyBEmployee();
result.Name = that.FullName;
result.PositionName = that.Title;
if(that.Children == null)
{
return result;
}
result.Children = new List<CompanyBEmployee>();
foreach(var item in that.Children)
{
result.Children.Add(item.ToCompanyBEmployee());
}
return result;
}
}
Good point is that you can write this without changing source code of classes CompanyBEmployee and CompanyAEmployee
So, this classes is not referenced one to another, but you can write converters From A to B and from B to A without cyclic references.
Here is the solution:
public class CompanyAEmployee
{
public string FullName { get; set; }
public string Title { get; set; }
public List<CompanyAEmployee> Children { get; set; }
public static explicit operator CompanyBEmployee(CompanyAEmployee employee)
{
CompanyBEmployee employee1 = new CompanyBEmployee();
employee1.Name = employee.FullName;
employee1.PositionName = employee.Title;
employee1.Children = new List<CompanyBEmployee>(employee.Children.Count);
int count = employee.Children.Count;
for (int i = 0; i < count; i++)
employee1.Children[i] = (CompanyBEmployee)employee.Children[i];
return employee1;
}
}
public class CompanyBEmployee
{
public string Name { get; set; }
public string PositionName { get; set; }
public List<CompanyBEmployee> Children { get; set; }
}
Now you can just use an assignment operator with explicit casting from CompanyAEmployee to CompanyBEmployee.
Like this:
CompanyAEmployee employee = new CompanyAEmployee();
//... assign all the fields
CompanyBEmployee employee2 = (CompanyBEmployee)employee.
Now you are done!
I have 3 model types:
public class BankA_Transaction : BanKTransactionMetaData
{
public string GROUPName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankB_Transaction : BanKTransactionMetaData
{
public string Name { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
public class BankC_Transaction : BanKTransactionMetaData
{
public string FullName { get; set; }
public string ACC_ID { get; set; }
public string ACCOUNT_NO { get; set; }
}
Note: The actual property lists are much longer
All of which inherit some fields needed when saving into the database.
public class BanKTransactionMetaData
{
public String BankName { get; set; }
}
These models get filled with records from a file sent by the bank and then saved to a database.
As part of this save I convert the records to JSON as that is required by the database.
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
string jsonText = JsonConvert.SerializeObject(bankA_Transaction, Formatting.Indented);
Code for saving...
At the moment I have a different methods for SaveBankA, SaveBankA and SaveBankB.
It seems to me that this is code replication and that I should get all the models to inherit better in order to use a base type? instead of each named type.
I've read up on Abstract and Virtual classes as I suspect that's what I need but I can't work out how to plug it together.
I can't just use Object in SaveBankA as I need to add .BankName.
Is there a better architecture to reduce code replication?
Perhaps you need something like this?
In base service class:
protected void SaveBankTransaction(BankTransactionMetaData tran)
{
string jsonText = JsonConvert.SerializeObject(tran, Formatting.Indented);
// additional saving code
}
In child service classes:
public void SaveBankA(BankA bankA)
{
bankA.BankName = "Bank_A";
base.SaveBankTransaction(bankA);
}
Create a couple of interfaces, one for your meta data (IBankData) and one for your bank transaction details (IBankTransaction). The IBankData interface will maintain a reference to the IBankTransaction interface. This should also allow you to add additional banks when needed, e.g. Bank D.
public interface IBankData
{
string BankName { get; }
// ... additional bank meta data properties
// ...
IBankTransaction Transaction { get; set; }
}
public interface IBankTransaction
{
[JsonProperty("ACC_ID")]
string AccountId { get; set; }
[JsonProperty("ACCOUNT_NO")]
string AccountNumber { get; set; }
// ... additional shared bank transaction properties
// ...
}
FYI, I chose to use the JsonProperty attribute to control the name for the JSON key, this allows the class properties to be named according to best practices without affecting the JSON property names.
Next implement the interfaces for each bank you will be working with. In each bank add the additional properties that will only apply to each implementation, i.e. since the GroupName property is only used by BankA, this property will be added to the BankA class and not the interface. The same goes for any other bank specific properties.
Bank A
public class BankA : IBankData
{
public string BankName => "BankA";
public IBankTransaction Transaction { get; set; }
}
public class BankATransaction : IBankTransaction
{
// Bank A specific properties
[JsonProperty("GROUPName")]
public string GroupName { get; set; }
// ... additional Bank A specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank B
public class BankB : IBankData
{
public string BankName => "BankB";
public IBankTransaction Transaction { get; set; }
}
public class BankBTransaction : IBankTransaction
{
// Bank B specific properties
public string Name { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
Bank C
public class BankC : IBankData
{
public string BankName => "BankC";
public IBankTransaction Transaction { get; set; }
}
public class BankCTransaction : IBankTransaction
{
// Bank B specific properties
public string FullName { get; set; }
// ... additional Bank B specific properties
// ...
// interface implemented properties
public string AccountId { get; set; }
public string AccountNumber { get; set; }
}
JsonConverter
Since the IBankTransaction is a property within the IBankData this will change your JSON structer. You may not want this, to retain your structure, a JsonConverter can be implemented on the IBankData interface. This will remove the Transaction object in the JSON and move the child properties under the JSON root.
public class BankJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
}
else
{
JObject o = (JObject)t;
JProperty transactionProperty = o.Properties().FirstOrDefault(p => p.Name == "Transaction");
o.Remove("Transaction");
JToken token = transactionProperty;
foreach (JToken ct in token.Children())
{
foreach (var prop in JProperty.FromObject(ct))
{
o.Add(prop);
}
}
serializer.Serialize(writer, o);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
}
public override bool CanRead => false;
public override bool CanConvert(Type objectType)
{
return objectType.GetInterfaces().Contains(typeof(IBankData));
}
}
Usage
For the usage example I've created a few test functions to prep the data and added SaveBank method that you can relocate in your actual code as it would make sense for your solution.
class Program
{
static void Main(string[] args)
{
string bankATransJson = GetBankATestJsonInput();
BankATransaction bankATransaction = JsonConvert.DeserializeObject<BankATransaction>(bankATransJson);
BankA bankA = new BankA();
bankA.Transaction = bankATransaction;
Console.WriteLine(SaveBank(bankA));
// output:
// {
// "BankName": "BankA",
// "GROUPName": "g54321",
// "ACC_ID": "A01",
// "ACCOUNT_NO": "A1111"
// }
string bankBInputJson = GetBankBTestJsonInput();
BankBTransaction bankBTransInput = JsonConvert.DeserializeObject<BankBTransaction>(bankBInputJson);
BankB bankB = new BankB();
bankB.Transaction = bankBTransInput;
Console.WriteLine(SaveBank(bankB));
// output:
// {
// "BankName": "BankB",
// "ACC_ID": "B02",
// "ACCOUNT_NO": "B2222",
// "Name": "Bank_Of_B
// }
string bankCInputJson = GetBankCTestJsonInput();
BankCTransaction bankCTransInput = JsonConvert.DeserializeObject<BankCTransaction>(bankCInputJson);
BankC bankC = new BankC();
bankC.Transaction = bankCTransInput;
Console.WriteLine(SaveBank(bankC));
// output:
// {
// "BankName": "BankC",
// "ACC_ID": "C03",
// "ACCOUNT_NO": "C3333",
// "FullName": "C Bank"
// }
}
public static string SaveBank(IBankData bankData)
{
// when calling the serialize object method, we pass our BankJsonConverter
string jsonText = JsonConvert.SerializeObject(bankData, Formatting.Indented, new BankJsonConverter());
// this example just returns the JSON text
// but you would implement your save logic as needed
return jsonText;
}
private static string GetBankATestJsonInput()
{
var obj = new { ACC_ID = "A01", ACCOUNT_NO = "A1111", GROUPName = "g54321" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankBTestJsonInput()
{
var obj = new { ACC_ID = "B02", ACCOUNT_NO = "B2222", Name = "Bank_Of_B" };
return JsonConvert.SerializeObject(obj);
}
private static string GetBankCTestJsonInput()
{
var obj = new { ACC_ID = "C03", ACCOUNT_NO = "C3333", FullName = "C Bank" };
return JsonConvert.SerializeObject(obj);
}
}
Trying to get the result from a webservice call to return a Model. I eep getting the error:
Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'CI.Models.Schedule' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
public Schedule getCourseSchedule()
{
var obj = new
{
States = new[] { new { State = "MX" } },
Zip = "",
Miles = "",
PaginationStart = 1,
PaginationLimit = 3
};
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "apoplication/json";
var url = "http://192.168.1.198:15014/ShoppingCart2/CourseSchedule";
var json = JsonConvert.SerializeObject(obj);
byte[] data = Encoding.UTF8.GetBytes(json);
byte[] result = client.UploadData(url, data);
string returnjson = Encoding.UTF8.GetString(result);
Schedule sched = JsonConvert.DeserializeObject<Schedule>(returnjson);
return sched;
}
}
Schedule Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Globalization;
namespace CI.Models
{
public class Schedule
{
public IEnumerable<Course> Courses { get; set; }
}
public class Course
{
/*
JSON Data returned from web service:
{
"ProgramGroup":"MR",
"ProgramCode":"RM",
"EventCode":"20160901MXMR",
"FormalDate":"September 1-2, 2016",
"StartDate":"2016\/09\/01",
"Price":5,
"LocName":"WB Hotel",
"LocAddress":"Av. Speedy Gonzales 220",
"LocCity":"Monterrey",
"LocState":"MX",
"LocZipCode":null,
"LicenseeURL":null,
"AgendaURL":"NA",
"SeatsAreAvailable":"2",
"GeneralInfoHTML":"General Info goes here.",
"GateKeeperHTML":null,
"EventType":"SS",
"TotalCourses":3
}
*/
public string ProgramGroup { get; set; }
public string ProgramCode { get; set; }
public string EventCode { get; set; }
public string FormalDate { get { return FormalDate; } set { FormalDate = convertFormalDateToSpanish(value); } }
public string StartDate { get; set; }
public double Price { get; set; }
public string LocName { get; set; }
public string LocAddress { get; set; }
public string LocCity { get ; set; }
public string LocState { get; set; }
public string LocZipCode { get; set; }
public string LicenseeURL { get; set; }
public string AgendaURL { get { return AgendaURL; } set { AgendaURL = buildAgendaLink(value); } }
public string SeatsAreAvailable { get; set; }
public string GeneralInfoHTML { get; set; }
public string GateKeeperHTML { get; set; }
public string EventType { get; set; }
public int TotalCourses { get; set; }
public string convertFormalDateToSpanish(string val)
{
DateTime TheDate = DateTime.Parse(StartDate);
string[] FormalDate = val.Split(" ".ToCharArray());
CultureInfo ci = new CultureInfo("es-ES");
string _Date = FormalDate[1].Replace("-", " al ").Replace(",", "");
string _Month = ci.TextInfo.ToTitleCase(TheDate.ToString("MMMM", ci));
val = string.Concat(_Date, " ", _Month);
return val;
}
private string buildAgendaLink(string val)
{
if (val.Trim() != "")
{
val = string.Concat("Agenda");
}
else
{
val = "Agenda";
}
return val;
}
}
}
Your server returns an array. Just try
Course[] courses = JsonConvert.DeserializeObject<Course[]>(returnjson);
Note that this is not an answer to your original problem, but I added it like an answer in order to explain my comment above with some actual code.
First problem with your code is that FormalDate and AgendaUrl properties simply won't work. Accessing them will result in a StackOverflowException, because you basically defined them recursively.
A property is merely syntax sugar for two separate getter/setter methods, so by writing this:
public class Course
{
public string FormalDate
{
get { return FormalDate; }
}
}
You are basically writing this:
public class Course
{
public string GetFormalDate()
{
// recursive call, with no terminating condition,
// will infinitely call itself until there is no
// more stack to store context data (and CLR
// will then throw an exception)
return GetFormalDate();
}
}
To fix that, you need to add an actual backing field, e.g.:
public class Course
{
private string _formalDate; // <-- this is a backing field;
// and this property uses the backing field to read/store data
public string FormalDate
{
get { return _formalDate; }
set { _formalDate = convertFormalDateToSpanish(value); }
}
}
Additionally, it's unusual for a property getter to return a different value than the one set through a setter. In other words, I would never expect this from a class:
var course = new Course();
course.StartDate = "2016/09/01";
course.FormalDate = "September 1-2, 2016";
Console.WriteLine(course.FormalDate); // prints "1 al 2 Septiembre" ?
I would rather move this functionality into a different class, or at least create different properties which return these values:
public class CourseInfo
{
// this is now a "dumb" auto-implemented property
// (no need for a backing field anymore)
public string FormalDate { get; set; }
// this read-only property returns the converted value
public string LocalizedFormalDate
{
get
{
return convertFormalDateToSpanish(FormalDate);
}
}
}