I have to serialize my objects to a XML document in a certain order.
<?xml version="1.0"?>
<__Root __version="1.1" __encryption="2">
<__service __serviceType="Test">
<__inputData>
<BaseToChange>
<__name>TestName</__name>
</BaseToChange>
</__inputData>
<__perform>
<__eventName>Event</__eventName>
</__perform>
<__inputData>
<ObjectChanges>
<Name>Test</Name>
</ObjectChanges>
</__inputData>
<__execute />
<__requestData>
<CompletionMsg />
</__requestData>
</__service>
</__Root>
The problem I have now is that I'm not able to serialize my List<InputData> with the Element Perform in between.
public class Service
{
[XmlAttribute("__serviceType")]
public string ServiceType { get; set; }
[XmlElement("__perform")]
public Perform Perform { get; set; }
[XmlElement("__inputData")]
public List<InputData> InputData{ get; set; }
[XmlElement("__execute")]
public Execute Execute { get; set; }
[XmlElement("__requestData")]
public RequestData RequestData{ get; set; }
public Service() { }
}
The order has to be as shown. So first <__inputData>, then <__perform>, followed by any remaining <__inputData>.
I already tried to separate the Properties and therefore the XmlElements but as soon I want to serialize with two elements having the same name I get an error.
Does anyone have an idea how to accomplish that?
You can take advantage of the polymorphic element functionality [XmlElement(Type = typeof(TElement))] to create a surrogate object array that contains both the Perform and InputData objects, in the correct sequence:
public class Service
{
[XmlAttribute("__serviceType")]
public string ServiceType { get; set; }
[XmlIgnore]
public Perform Perform { get; set; }
[XmlIgnore]
public List<InputData> InputData { get; set; }
[XmlElement("__perform", Type = typeof(Perform))]
[XmlElement("__inputData", Type = typeof(InputData))]
public object[] XmlInputAndPerformance
{
get
{
var inputData = (InputData ?? Enumerable.Empty<InputData>()).Cast<object>();
var performData = Perform == null ? Enumerable.Empty<object>() : new object[] { Perform };
return inputData.Take(1)
.Concat(performData)
.Concat(inputData.Skip(1))
.ToArray();
}
set
{
if (value == null)
return;
var newInputs = value.OfType<InputData>().ToList();
var newPerform = value.OfType<Perform>().ToList();
if (newInputs.Count + newPerform.Count != value.Length)
throw new ArgumentException("Unknown type.");
if (newPerform.Count > 1)
throw new ArgumentException("Too many Perform objects.");
if (newPerform.Count > 0)
Perform = newPerform[0];
(InputData = InputData ?? new List<InputData>()).AddRange(newInputs);
}
}
[XmlElement("__execute")]
public Execute Execute { get; set; }
[XmlElement("__requestData")]
public RequestData RequestData { get; set; }
public Service() { }
}
Note that the original InputData and Perform properties have been marked with [XmlIgnore] and that two [XmlElement(name, Type = typeof(TElement))] attributes have been added, one for InputData and one for Perform.
Sample fiddle.
Related
I am trying to serialize a C# object into XML so that it could be used as the body of API call. They are very particular about the input they need. I have built the following class to hold the data I need to send over to them. Including attributes and all properties as Elements instead of attributes. They also require that lists include the type="array" I though that creating my own class the implements a List would be the easiest since all lists I give them must have the same attribute. When serialization occurs it serializes the base class of List items but it doesn't include the attribute I want from the derived class.
public class CustomArray<T> : List<T>
{
[XmlAttribute]
public string type { get; set; } = "array";
}
[XmlRoot("message")]
public class MessageBody
{
[XmlArray("Checks"), XmlArrayItem("CheckItem")]
public CustomArray<Check> CheckList { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
[XmlArray("InvoiceList"), XmlArrayItem("Invoice")]
public CustomArray<Invoice> InvoiceList { get; set; }
}
public class Invoice
{
[XmlElement("C_INVOICE_ID")]
public long ID { get; set; }
[XmlElement("C_INVOICE_NUM")]
public string InvoiceNum { get; set; }
}
I then run this code:
// Create a sample object
var message = new MessageBody()
{
CheckList = new CustomArray<Check>
{
new Check
{
CheckNumber = "111",
Amount = 1.00M
},
new Check
{
CheckNumber = "112",
Amount = 2.00M,
InvoiceList = new CustomArray<Invoice>
{
new Invoice
{
ID = 1,
InvoiceNum = "1"
}
}
}
}
};
// Create custom settings
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
// Serialize item and print it to console
using (var sw = new StringWriter())
using (var writer = XmlWriter.Create(sw, settings))
{
var serializer = new XmlSerializer(message.GetType());
serializer.Serialize(writer, message, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
Console.WriteLine(sw.ToString());
}
I get this written to the console:
<message>
<Checks>
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList>
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
But I need to get this:
<message>
<Checks type="array">
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList type="array">
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
Thank you for your help!
Here is a dotnetfiddle that I made to show it off. It's not exact but it has the same idea. https://dotnetfiddle.net/ALCX5H
Try following :
[XmlRoot("message")]
public class MessageBody
{
[XmlElement("Checks")]
public Checks Checks { get; set; }
}
public class Checks
{
[XmlAttribute]
public string type { get; set; }
[XmlElement("Checks")]
public List<Check> Checks { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
}
I have this response from server
[{
"sys_id": "******************************",
"dv_model_id": "*****************",
"due": "YYYY-MM-DD HH:mm:ss",
"assigned_to": "1524s32a54dss412s121s",
"dv_assigned_to": "username",
"assigned_to.phone": "+12345678910",
"assigned_to.email": "abc#a.c",
"u_borrower_id": "fb36e45f0a12452004742183457e833b0",
"dv_u_borrower_id": "antoherUserName",
"u_borrower_id.phone": "+12345678910",
"u_borrower_id.email": "abcf#a.c"
}
,{....}
,{....}]
I'm trying to deserialize this to List
public class Inventory
{
public Inventory()
{
assigned_to = new User();
u_borrower_wwid = new User();
}
public string sys_ID { get; set; }
public string dv_model_id { get; set; }
public DateTime due { get; set; }
public string dv_assigned_to { get; set; }
public User assigned_to { get; set; }
public string dv_u_borrower_id { get; set; }
public User u_borrower_id { get; set; }
}
now, since the JSON contains - "assigned_to": "1524s32a54dss412s121s","
the deserialization failed.
the same with the - ""u_borrower_id": "fb36e45f0a12452004742183457e833b0"," .
do you know any way to ignore them? or remove them from the JSON?
I need only the properties (".phone" and ".email") of the object.
any ideas?
I see a number of solutions:
Modify your Inventory object (or create a new one) so the json can be fully deserialized, then access the values there.
Your new and updated object should look like this:
public class InventoryJsonObject
{
public string sys_id { get; set; }
public string dv_model_id { get; set; }
public string due { get; set; }
public string assigned_to { get; set; }
public string dv_assigned_to { get; set; }
[JsonProperty("assigned_to.phone")]
public string assigned_to_phone { get; set; }
[JsonProperty("assigned_to.email")]
public string assigned_to_email { get; set; }
public string u_borrower_id { get; set; }
public string dv_u_borrower_id { get; set; }
[JsonProperty("u_borrower_id.phone")]
public string u_borrower_id_phone { get; set; }
[JsonProperty("u_borrower_id.email")]
public string u_borrower_id_email { get; set; }
}
Use regular expressions to get the values from the string. In this case your regex would be "u_borrower_id\.phone": "(.*?)" and "u_borrower_id\.email": "(.*?)"
The complete regex solution could look like this (assuming every object has a phone and email included):
string phonePattern = "\"u_borrower_id\\.phone\": \"(.*?)\"";
string emailPattern = "\"u_borrower_id\\.email\": \"(.*?)\"";
Regex phoneRegex = new Regex(phonePattern);
var phoneMatches = phoneRegex.Matches(input);
Regex emailRegex = new Regex(emailPattern);
var emailMatches = emailRegex.Matches(input);
for (int i = 0; i < phoneMatches.Count; i++)
{
string phoneMatch = phoneMatches[i].Groups[1].Value;
string emailMatch = emailMatches[i].Groups[1].Value;
// Now you can add them to any collection you desire
}
Implement a cast between string and User. Since your error originates from the fact that the string fb36e45f0a12452004742183457e833b0 cannot be cast into a User object trivially, you have to implement the cast. It would look like this:
public static implicit operator User(string _string)
{
// This could be a DB lookup, or basically anything else
return new User()
{
id = _string
};
}
In order to avoid unnecessary casting that causes bug you can use this workaround by creating a Dictionary<string, object>
and read only the properties you need as strings and convert them to your desired type:
using System.Web.Script;
Dictionary<string, object> dict = Serialization.JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
Now you can modify your class properties one by one like:
(You can create additional method for your DateTime and User property)
Inventory inventory = new Inventory();
//notice i'v added the 'u_borrower_id_email' property to your class:
inventory.u_borrower_id_email = dict.GetStringOrDefault("u_borrower_id.phone");
private static string GetStringOrDefault(this Dictionary<string, object> data, string key)
{
string result = "";
object o;
if (data.TryGetValue(key, out o))
{
if (o != null)
{
result = o.ToString();
}
}
return result;
}
The XML below always comes in this format, but the elements under the <Hit> node are dynamic, the name or number of items could be different each time. Is it possible to get the elements under the <Hit> node dynamically.
<SearchResponse>
<Response>
<Qtime>3</Qtime>
<HitsPerPage>10</HitsPerPage>
</Response>
<HitsCount>
<total>33</total>
<start>0</start>
<end>10</end>
</HitsCount>
<Hits>
<Hit>
<id type=''>123</id>
<eid type=''>456</eid>
<Title type='t'>
<![CDATA[title goes here]]>
</Title>
</Hit>
<Hit>
<id type=''>123</id>
<oid type=''>456</oid>
<Title type='t'>
<![CDATA[title goes here]]>
</Title>
<Description type='s'>
<![CDATA[Description goes here]]>
</Description>
</Hit>
</Hits>
</SearchResponse>
Edit: here's the C# code, it's working fine and get the id from the <Hit> node since I already defined the property, but I need to get all of them dynamic.
[XmlRoot("SearchResponse")]
public sealed class SearchResponse {
[XmlElement("Response", Type = typeof(Response))]
public Response[] Responses { get; set; }
[XmlElement("HitsCount", Type = typeof(HitsCount))]
public HitsCount[] HitsCount { get; set; }
[XmlElement("Hits", Type = typeof(Hits))]
public Hits[] Hits { get; set; }
public static SearchResponse GetSearchResponseObject(string xmlString) {
try {
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(SearchResponse));
var instance = (SearchResponse)serializer.Deserialize(reader);
return instance;
} catch (Exception ex) {
var asd = ex.Message;
return null;
}
}
}
[Serializable]
public class Response {
[XmlElement("Qtime")]
public string Qtime { get; set; }
[XmlElement("HitsPerPage")]
public string HitsPerPage { get; set; }
}
[Serializable]
public class HitsCount {
[XmlElement("total")]
public string Total { get; set; }
[XmlElement("start")]
public string Start { get; set; }
[XmlElement("end")]
public string End { get; set; }
}
[Serializable]
public class Hits {
[XmlElement("Hit")]
public Hit[] Hit { get; set; }
}
[Serializable]
public class Hit {
[XmlElement("id")]
public string Id { get; set; }
}
Edit 2: // Hit class code
public class Hit {
// Since "id" is expected in the XML, deserialize it explicitly.
[XmlElement("id")]
public string Id { get; set; }
private readonly List<XElement> _elements = new List<XElement>();
[XmlAnyElement]
public List<XElement> Elements { get { return _elements; } }
}
Since you don't know what elements might be present in your Hit class, you can add a List<XElement> property to you class and attach the [XmlAnyElement] attribute to it. It will then capture any and all unknown elements in the XML for the class. Once the elements are deserialized, you can add API properties to query for elements with specific names, for instance:
public class Hit
{
// Since "id" is expected in the XML, deserialize it explicitly.
[XmlElement("id")]
public string Id { get; set; }
private readonly List<XElement> elements = new List<XElement>();
[XmlAnyElement]
public List<XElement> Elements { get { return elements; } }
#region convenience methods
public string this[XName name]
{
get
{
return Elements.Where(e => e.Name == name).Select(e => e.Value).FirstOrDefault();
}
set
{
var element = Elements.Where(e => e.Name == name).FirstOrDefault();
if (element == null)
Elements.Add(element = new XElement(name));
element.Value = value;
}
}
const string title = "Title";
[XmlIgnore]
public string Title
{
get
{
return this[title];
}
set
{
this[title] = value;
}
}
#endregion
}
Incidentally, you can eliminate your Hits class if you mark the Hits array with [XmlArray] rather than [XmlElement], like so:
[XmlRoot("SearchResponse")]
public sealed class SearchResponse
{
[XmlElement("Response", Type = typeof(Response))]
public Response[] Responses { get; set; }
[XmlElement("HitsCount", Type = typeof(HitsCount))]
public HitsCount[] HitsCount { get; set; }
[XmlArray("Hits")] // Indicates that the hits will be serialized with an outer container element named "Hits".
[XmlArrayItem("Hit")] // Indicates that each inner entry element will be named "Hit".
public Hit [] Hits { get; set; }
}
Update
The
public string this[XName name] { get; set; }
Is an indexer. See Using Indexers (C# Programming Guide). I added it so it would be easy to do things like:
var description = hit["Description"];
var title = hit["Title"];
The indexer looks for the first XML element with the specified name, and returns its text value. If you don't want it, you can leave it out -- it's just for convenience.
I have a model structure as illustrate below.
public class GuideLineSectionsViewModel
{
public GuideLineSectionsViewModel()
{
SectionsSet = new List<SectionViewModel>();
}
public string Title { get; set; }
public List<SectionViewModel> SectionsSet { get; set; }
}
public class SectionViewModel
{
public SectionViewModel()
{
SectionsSet = new List<SectionViewModel>();
QuestionsSet = new List<QuestionViewModel>();
ProblemsSet = new List<ProblemViewModel>();
GoalsSet = new List<GoalViewModel>();
BarriersSet = new List<BarriersViewModel>();
QuestionReferencesSet = new List<QuestionReferenceViewModel>();
}
public string Heading { get; set; }
public List<SectionViewModel> SectionsSet { get; set; }
public List<QuestionViewModel> QuestionsSet { get; set; }
public List<ProblemViewModel> ProblemsSet { get; set; }
public List<GoalViewModel> GoalsSet { get; set; }
public List<BarriersViewModel> BarriersSet { get; set; }
public List<QuestionReferenceViewModel> QuestionReferencesSet { get; set; }
}
public class ProblemViewModel
{
public string Text { get; set; }
public bool Identified { get; set; }
public List<GoalViewModel> GoalsSet { get; set; }
public List<QuestionReferenceViewModel> QuestionReferencesSet { get; set; }
}
Now Based on the condition I need to update the every list value of the ProblemViewModel using linq.Below is the condition
public GuideLineSectionsViewModel FindGuidelineType(GuideLineSectionsViewModel guidelineSectionModel)
{
//GuideLineSectionsViewModel result = new GuideLineSectionsViewModel();
string title = guidelineSectionModel.Title;
int count = Regex.Matches(title, "Low Intensity").Count;
if (count > 0)
{
}
return guidelineSectionModel;
}
The guidelineSectionModel.Title will contain the text as "some value : Low Intensity". So i used the regx to filter the text. Is there other way i can directly check the condition in linq. and update the model model.
I want to update list value of ProblemViewModelmodel property value public bool Identified to "true"
Currently it contain only False value.
Please can anyone help me to solve the issue.
Have a look at following method. I could not put LINQ but I think this answer can solve your purpose. Again Some classes structure are missing in your question so you may need to put that in following method.
GuideLineSectionsViewModel FindGuidelineType(GuideLineSectionsViewModel guidelineSectionModel)
{
//GuideLineSectionsViewModel result = new GuideLineSectionsViewModel();
string title = guidelineSectionModel.Title;
int count = Regex.Matches(title, "Low Intensity").Count;
if (count > 0)
{
foreach(SectionViewModel svm in guidelineSectionModel.SectionsSet)
{
foreach(ProblemViewModel pvm in svm.ProblemsSet)
{
pvm.Identified = true;
}
}
}
return guidelineSectionModel;
}
If you prefer LINQ:
if(guideLine.Title.Contains("Low Intensity"))
{
guideLine.SectionsSet.ForEach(s => s.ProblemsSet.ForEach(ps => ps.Identified = true));
}
Note: please read this answer https://stackoverflow.com/a/2962689/1525637 due to possible performance problems with the Regex.Matches, you should use String.Contains instead.
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);
}
}
}