{
"TestData":{
"Year__of__Account":"2019",
"Tax___x0025_":"0.06",
"Buildings__1":"1000",
"Contents__1":"400",
"Total_Insurable_Value":"100",
"Buildings__Prem":"2560.8",
"Contents__Prem":"1707.2",
"YB__1":"1950",
"No__Buildings":"55",
"Location_Sprinklers_YN":"No",
"test":"test"
}
}
In the above sample JSON I want to add a property called "Name" with Value "John" inside property "TestData". How can I achieve this using .net Core 3.0 System.Text.Json library.
I have tried using methods of Utf8JsonWriter but it is creating a new JSON object instead of appending it to the above existing JSON.
using (MemoryStream memoryStream1 = new MemoryStream())
{
using (Utf8JsonWriter utf8JsonWriter1 = new Utf8JsonWriter(memoryStream1))
{
using (JsonDocument jsonDocument = JsonDocument.Parse(json))
{
utf8JsonWriter1.WriteStartObject();
utf8JsonWriter1.WritePropertyName("Name");
utf8JsonWriter1.WriteStringValue("John");
utf8JsonWriter1.WriteEndObject();
// how can I add above properties to JsonDocument object??
}
}
}
Starting from .NET 6 you can use JsonNode. This is a modifiable, dictionary-backed API to complement the readonly JsonDocument.
For your example, the solution would be as follows:
var jsonNode = JsonNode.Parse(json);
jsonNode["TestData"]["Name"] = "John";
Assuming there may be several properties and you want to add a name only to "TestData" property:
using (MemoryStream memoryStream1 = new MemoryStream())
{
using (Utf8JsonWriter utf8JsonWriter1 = new Utf8JsonWriter(memoryStream1))
{
using (JsonDocument jsonDocument = JsonDocument.Parse(json))
{
utf8JsonWriter1.WriteStartObject();
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
if (element.Name == "TestData")
{
utf8JsonWriter1.WritePropertyName(element.Name);
// Staring new object
utf8JsonWriter1.WriteStartObject();
// Adding "Name" property
utf8JsonWriter1.WritePropertyName("Name");
utf8JsonWriter1.WriteStringValue("John");
// Copying existing values from "TestData" object
foreach (var testDataElement in element.Value.EnumerateObject())
{
testDataElement.WriteTo(utf8JsonWriter1);
}
utf8JsonWriter1.WriteEndObject();
}
else
{
element.WriteTo(utf8JsonWriter1);
}
}
utf8JsonWriter1.WriteEndObject();
}
}
var resultJson = Encoding.UTF8.GetString(memoryStream1.ToArray());
}
Here for each property (except for "TestData" property) I write the whole value as is (by calling element.WriteTo(utf8JsonWriter1)), and for "TestData" property I start a new object, add "Name" property and then copy each of the "TestData" object's properties.
P.S. This works, but I'm pretty sure a much better solution should exist.
Here is a possible answer
static void Main(string[] args)
{
var jsonString = #"
{
""TestData"":{
""Year__of__Account"":""2019"",
""Tax___x0025_"":""0.06"",
""Buildings__1"":""1000"",
""Contents__1"":""400"",
""Total_Insurable_Value"":""100"",
""Buildings__Prem"":""2560.8"",
""Contents__Prem"":""1707.2"",
""YB__1"":""1950"",
""No__Buildings"":""55"",
""Location_Sprinklers_YN"":""No"",
""test"":""test""
}
}
";
var jsonDoc = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
var testDataDict = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonDoc["TestData"].ToString());
testDataDict.Add("Name", "John");
//replace the test data with the modified test data
jsonDoc["TestData"] = testDataDict;
Console.WriteLine(JsonSerializer.Serialize(jsonDoc));
}
I've just created a NuGet package with some hopefully useful extension methods for JsonElement, which allow properties to be added and/or removed. It's based on using the Utf8JsonWriter to create a new mutated JsonElement based on the original, like the answer above.
GitHub repo | NuGet package
var jsonString = "{ \"Name\": \"Andrew\", \"EmailAddress\": \"a#b.com\" }";
var jElement = JsonDocument.Parse(jsonString).RootElement;
jElement = jElementAddProperty("Age", 38)
.AddProperty("Male", true)
.AddProperty("Female", false)
.AddNullProperty("Alien")
.AddProperty("Roles", new string[] { "admin", "user" })
.AddProperty("LastUpdated", DateTime.UtcNow)
.AddProperty("crazyNewObject", new
{
Name = "Hobbies",
Value = "bass guitar and writing c# code"
});
Hopefully someone will find them useful, but if they don't do quite what you need, please enhance and submit a pull request.
Using JsonSerializer to deserialize into a nested dictionary is also possible:
static void Main(string[] args)
{
string testJson = #"
{
""TestData"":{
""Year__of__Account"":""2019"",
""Tax___x0025_"":""0.06"",
""Buildings__1"":""1000"",
""Contents__1"":""400"",
""Total_Insurable_Value"":""100"",
""Buildings__Prem"":""2560.8"",
""Contents__Prem"":""1707.2"",
""YB__1"":""1950"",
""No__Buildings"":""55"",
""Location_Sprinklers_YN"":""No"",
""test"":""test""
}
}";
using (var memoryStream1 = new MemoryStream())
{
using (var utf8JsonWriter1 = new Utf8JsonWriter(memoryStream1))
{
//For each level in json tree an additional dictionary must be added
var jsonDict = JsonSerializer.Deserialize<Dictionary<string, Dictionary<string, object>>>(testJson);
jsonDict["TestData"].Add("Name", "John");
JsonSerializer.Serialize<object>(utf8JsonWriter1, jsonDict);
}
string testString = Encoding.UTF8.GetString(memoryStream1.ToArray());
}
}
But note that the new property is always added at the end of the TestData block.
Related
I have a dictionary of abilityobjects <id, abilityObj> that I'm trying to serialize in XML. Because you can't XML Serialize a dictionary, I change it into a list on serialization
public class BattleSerializable : TyrantSerializable
{
[XmlIgnoreAttribute]
[NonSerialized]
[DoNotSerialize]
public Dictionary<int, AbilityObjectSerializable> battleObjects;
public List<AbilityObjectSerializable> serializedBattleObjects
{
get
{
if (battleObjects == null)
{
battleObjects = new Dictionary<int, AbilityObjectSerializable>();
}
return battleObjects.Select(x => x.Value).ToList();
}
set
{
battleObjects = value.ToDictionary(x => x.entityId, x => x);
}
}
It serializes correctly. I.e. the XML that gets saved makes sense
<BattleSerializable>
...
<serializedBattleObjects>
<AbilityObjectSerializable xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" d3p1:type="FireballObject">
<hexDirection>southeast</hexDirection>
<gridX>0</gridX>
<gridZ>7</gridZ>
<entityId>3</entityId>
<lastAnimation>STATUE</lastAnimation>
<timer>0</timer>
<abilityPos>2</abilityPos>
<abilityType>FIREBALL</abilityType>
<health>100</health>
<tilesPerTurn>2</tilesPerTurn>
<jump>1</jump>
<fall>99</fall>
<damage>5</damage>
<lineTraversed>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</lineTraversed>
<moveDirection>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</moveDirection>
</AbilityObjectSerializable>
</serializedBattleObjects>
</BattleSerializable>
But when I try to then -load- this XML and turn it into actual C# objects, this list comes in empty for some reason, causing the app to blow up.
What am I missing? All the other lists in this class serialize/deserialize correctly.
My load code:
public BattleSerializable Load(string path)
{
var serializer = new XmlSerializer(typeof(BattleSerializable));
try
{
using (var stream = new FileStream(path, FileMode.Open))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
string xmlString = xmlDoc.InnerXml;
BattleSerializable bs = (BattleSerializable)this.LoadFromXML(xmlString);
return bs;
}
}
catch (Exception e)
{
throw new SettingLoadException("Settings failed validation");
}
}
The way a lot of serializers work is by calling Add on a list, only actually assigning anything back to the setter if the serializer created the list (perhaps because it was null, or fixed size such as an array). So imagine the serializer doing:
var list = obj.SomeProperty;
while (moreOfTheSame)
list.Add(ReadOneOfThose());
It never calls the setter, so any logic in there: irrelevant. You'll probably need a custom list type here, or perhaps more simply: have a nice simple POCO/DTO model that just maps to the serialization shape with no fun logic, and project between this model and your domain model separately to serialization.
I am trying to programmatically add a parent-child relationship between two work items. I am using the Microsoft Team Foundation and Visual Studio Services libraries to export and import TFS 2015 and VSTS backlog objects.
https://learn.microsoft.com/en-us/vsts/integrate/concepts/dotnet-client-libraries
https://www.visualstudio.com/en-us/docs/integrate/api/wit/samples#migrating-work-items
I have worked through obtaining a VssConnection to my servers and getting a WorkItemTrackingHttpClient to execute Wiql queries and create work items. I also have a query to identify the parent of a target work item.
What I cannot figure out is how to add the link between child work items and their parent. I do not know the correct JsonPatchDocument item path to add the parent, or the correct property or method on an existing WorkItem to update it with a parent link. Does anyone have documentation links or specific information on adding a parent relationship to a work item using these libraries?
Here are some code excerpts for context:
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
// ...
var sourceConnection = new VssConnection(new Uri(_sourceTsUrl), new VssClientCredentials());
var targetConnection = new VssConnection(new Uri(_targetTsUrl), new VssClientCredentials());
var sourceClient = sourceConnection.GetClient<WorkItemTrackingHttpClient>();
var targetClient = targetConnection.GetClient<WorkItemTrackingHttpClient>();
// ...
var queryResults = sourceClient.QueryByWiqlAsync(query).Result;
var ids = queryResults.WorkItems.Select(x => x.Id).ToList();
var items = sourceClient.GetWorkItemsAsync(ids);
foreach (var item in items.Result)
{
// ...
var patchItem = new JsonPatchDocument();
foreach (var fieldName in item.Fields.Keys)
{ patchItem.Add(new JsonPatchOperation() { Path = $"/fields/{fieldName}", Value = item.Fields[fieldName], Operation = Operation.Add }); }
// TODO - add patch field(?) for parent relationship
var parentResults = sourceClient.QueryByWiqlAsync(parentQuery).Result;
// ...
var task = targetClient.CreateWorkItemAsync(patchItem, targetProject, itemType, validateOnly, bypassRules, suppressNotifications);
var newItem = task.Result;
// TODO - alternatively, add parent via the returned newly generated WorkItem
}
Addendum:
I've tried adding the following code, but the changes do not get committed to the remote object, it only exists in local memory, and I cannot find a method to push the changes/updates.
if (!string.IsNullOrWhiteSpace(mappedParentUrl))
{
if (newItem.Relations == null)
{ newItem.Relations = new List<WorkItemRelation>(); }
newItem.Relations.Add(new WorkItemRelation() { Rel = "Parent", Title = mappedParentTitle, Url = mappedParentUrl });
}
Refer to this code to create task work item with parent link (Update it to meet your requirement):
var url = new Uri("https://XXX.visualstudio.com");
var connection = new VssConnection(url, new VssClientCredentials());
var workitemClient = connection.GetClient<WorkItemTrackingHttpClient>();
string projectName = "[project name]";
int parentWITId = 771;//parent work item id
var patchDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation() {
Operation=Operation.Add,
Path= "/fields/System.Title",
Value="parentandchildWIT"
});
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new
{
rel = "System.LinkTypes.Hierarchy-Reverse",
url = connection.Uri.AbsoluteUri+ projectName+ "/_apis/wit/workItems/"+parentWITId,
attributes = new
{
comment = "link parent WIT"
}
}
});
var createResult = workitemClient.CreateWorkItemAsync(patchDocument, projectName, "task").Result;
So what I really want is somewhat usable tab completion in a PS module.
ValidateSet seems to be the way to go here.
Unfortunately my data is dynamic, so I cannot annotate the parameter with all valid values upfront.
DynamicParameters/IDynamicParameters seems to be the solution for that problem.
Putting these things together (and reducing my failure to a simple test case) we end up with:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace PSDummy
{
[Cmdlet(VerbsCommon.Get, "BookDetails")]
public class GetBookDetails : Cmdlet, IDynamicParameters
{
IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]> {
{"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
{"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}}
};
private RuntimeDefinedParameter m_authorParameter;
private RuntimeDefinedParameter m_bookParameter;
protected override void ProcessRecord()
{
// Do stuff here..
}
public object GetDynamicParameters()
{
var parameters = new RuntimeDefinedParameterDictionary();
m_authorParameter = CreateAuthorParameter();
m_bookParameter = CreateBookParameter();
parameters.Add(m_authorParameter.Name, m_authorParameter);
parameters.Add(m_bookParameter.Name, m_bookParameter);
return parameters;
}
private RuntimeDefinedParameter CreateAuthorParameter()
{
var p = new RuntimeDefinedParameter(
"Author",
typeof(string),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 0,
Mandatory = true
},
new ValidateSetAttribute(m_dummyData.Keys.ToArray()),
new ValidateNotNullOrEmptyAttribute()
});
// Actually this is always mandatory, but sometimes I can fall back to a default
// value. How? p.Value = mydefault?
return p;
}
private RuntimeDefinedParameter CreateBookParameter()
{
// How to define a ValidateSet based on the parameter value for
// author?
var p = new RuntimeDefinedParameter(
"Book",
typeof(string),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 1,
Mandatory = true
},
new ValidateSetAttribute(new string[1] { string.Empty }/* cannot fill this, because I cannot access the author */),
new ValidateNotNullOrEmptyAttribute()
});
return p;
}
}
}
Unfortunately this tiny snippet causes a lot of issues already. Ordered descending:
I fail to see how I can create a connection between the parameters. If you pick an author, you should only be able to pick a book that matches the author. So far GetDynamicParameters() always seems stateless though: I see no way to access the value of a different/earlier dynamic parameter. Tried keeping it in a field, tried searching MyInvocation - no luck. Is that even possible?
How do you define a default value for mandatory parameter? Doesn't fit the silly example, but let's say you can store your favorite author. From now on I want to default to that author, but having a pointer to an author is still mandatory. Either you gave me a default (and can still specify something else) or you need to be explicit.
Tab completion for strings with spaces seems weird/broken/limited - because it doesn't enclose the value with quotes (like cmd.exe would do, for example, if you type dir C:\Program <tab>). So tab completion actually breaks the invocation (if the issues above would be resolved, Get-BookDetails Ter<tab> would/will expand to Get-BookDetails Terry Pratchett which puts the last name in parameter position 1 aka 'book'.
Shouldn't be so hard, surely someone did something similar already?
Update: After another good day of tinkering and fooling around I don't see a way to make this work. The commandlet is stateless and will be instantiated over and over again. At the point in time when I can define dynamic parameters (GetDynamicParameters) I cannot access their (current) values/see what they'd be bound to - e.g. MyInvocation.BoundParameters is zero. I'll leave the question open, but it seems as if this just isn't supported. All the examples I see add a dynamic parameter based on the value of a static one - and that's not relevant here. Bugger.
I think this works. Unfortunately, it uses reflection to get at some of the cmdlet's private members for your first bullet. I got the idea from Garrett Serack. I'm not sure if I completely understood how to do the default author, so I made it so that the last valid author is stored in a static field so you don't need -Author the next time.
Here's the code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace PSDummy
{
internal class DynParamQuotedString {
/*
This works around the PowerShell bug where ValidateSet values aren't quoted when necessary, and
adding the quotes breaks it. Example:
ValidateSet valid values = 'Test string' (The quotes are part of the string)
PowerShell parameter binding would interperet that as [Test string] (no single quotes), which wouldn't match
the valid value (which has the quotes). If you make the parameter a DynParamQuotedString, though,
the parameter binder will coerce [Test string] into an instance of DynParamQuotedString, and the binder will
call ToString() on the object, which will add the quotes back in.
*/
internal static string DefaultQuoteCharacter = "'";
public DynParamQuotedString(string quotedString) : this(quotedString, DefaultQuoteCharacter) {}
public DynParamQuotedString(string quotedString, string quoteCharacter) {
OriginalString = quotedString;
_quoteCharacter = quoteCharacter;
}
public string OriginalString { get; set; }
string _quoteCharacter;
public override string ToString() {
// I'm sure this is missing some other characters that need to be escaped. Feel free to add more:
if (System.Text.RegularExpressions.Regex.IsMatch(OriginalString, #"\s|\(|\)|""|'")) {
return string.Format("{1}{0}{1}", OriginalString.Replace(_quoteCharacter, string.Format("{0}{0}", _quoteCharacter)), _quoteCharacter);
}
else {
return OriginalString;
}
}
public static string[] GetQuotedStrings(IEnumerable<string> values) {
var returnList = new List<string>();
foreach (string currentValue in values) {
returnList.Add((new DynParamQuotedString(currentValue)).ToString());
}
return returnList.ToArray();
}
}
[Cmdlet(VerbsCommon.Get, "BookDetails")]
public class GetBookDetails : PSCmdlet, IDynamicParameters
{
IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) {
{"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
{"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}},
{"An 'Author' (notice the ')", new [] {"A \"book\"", "Another 'book'","NoSpace(ButCharacterThatShouldBeEscaped)", "NoSpace'Quoted'", "NoSpace\"Quoted\""}} // Test value I added
};
protected override void ProcessRecord()
{
WriteObject(string.Format("Author = {0}", _author));
WriteObject(string.Format("Book = {0}", ((DynParamQuotedString) MyInvocation.BoundParameters["Book"]).OriginalString));
}
// Making this static means it should keep track of the last author used
static string _author;
public object GetDynamicParameters()
{
// Get 'Author' if found, otherwise get first unnamed value
string author = GetUnboundValue("Author", 0) as string;
if (!string.IsNullOrEmpty(author)) {
_author = author.Trim('\'').Replace(
string.Format("{0}{0}", DynParamQuotedString.DefaultQuoteCharacter),
DynParamQuotedString.DefaultQuoteCharacter
);
}
var parameters = new RuntimeDefinedParameterDictionary();
bool isAuthorParamMandatory = true;
if (!string.IsNullOrEmpty(_author) && m_dummyData.ContainsKey(_author)) {
isAuthorParamMandatory = false;
var m_bookParameter = new RuntimeDefinedParameter(
"Book",
typeof(DynParamQuotedString),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 1,
Mandatory = true
},
new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData[_author])),
new ValidateNotNullOrEmptyAttribute()
}
);
parameters.Add(m_bookParameter.Name, m_bookParameter);
}
// Create author parameter. Parameter isn't mandatory if _author
// has a valid author in it
var m_authorParameter = new RuntimeDefinedParameter(
"Author",
typeof(DynParamQuotedString),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 0,
Mandatory = isAuthorParamMandatory
},
new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData.Keys.ToArray())),
new ValidateNotNullOrEmptyAttribute()
}
);
parameters.Add(m_authorParameter.Name, m_authorParameter);
return parameters;
}
/*
TryGetProperty() and GetUnboundValue() are from here: https://gist.github.com/fearthecowboy/1936f841d3a81710ae87
Source created a dictionary for all unbound values; I had issues getting ValidateSet on Author parameter to work
if I used that directly for some reason, but changing it into a function to get a specific parameter seems to work
*/
object TryGetProperty(object instance, string fieldName) {
var bindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public;
// any access of a null object returns null.
if (instance == null || string.IsNullOrEmpty(fieldName)) {
return null;
}
var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);
if (propertyInfo != null) {
try {
return propertyInfo.GetValue(instance, null);
}
catch {
}
}
// maybe it's a field
var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);
if (fieldInfo!= null) {
try {
return fieldInfo.GetValue(instance);
}
catch {
}
}
// no match, return null.
return null;
}
object GetUnboundValue(string paramName) {
return GetUnboundValue(paramName, -1);
}
object GetUnboundValue(string paramName, int unnamedPosition) {
// If paramName isn't found, value at unnamedPosition will be returned instead
var context = TryGetProperty(this, "Context");
var processor = TryGetProperty(context, "CurrentCommandProcessor");
var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;
if (args != null) {
var currentParameterName = string.Empty;
object unnamedValue = null;
int i = 0;
foreach (var arg in args) {
var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
if (isParameterName != null && true.Equals(isParameterName)) {
string parameterName = TryGetProperty(arg, "ParameterName") as string;
currentParameterName = parameterName;
continue;
}
// Treat as a value:
var parameterValue = TryGetProperty(arg, "ArgumentValue");
if (currentParameterName != string.Empty) {
// Found currentParameterName's value. If it matches paramName, return
// it
if (currentParameterName.Equals(paramName, StringComparison.OrdinalIgnoreCase)) {
return parameterValue;
}
}
else if (i++ == unnamedPosition) {
unnamedValue = parameterValue; // Save this for later in case paramName isn't found
}
// Found a value, so currentParameterName needs to be cleared
currentParameterName = string.Empty;
}
if (unnamedValue != null) {
return unnamedValue;
}
}
return null;
}
}
}
I have a project similar(Almost identical) to Conference API project which is taking similar approach to the noted project for returning CollectionJson content. I am having difficulty Setting the Collection property of the ReadDocument (Line 30) as it does not have any setter. I could bypass this problem by doing the following change
public CollectionJsonContent(Collection collection)
{
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
Formatting = Newtonsoft.Json.Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
collection.Version = "1.0";
Headers.ContentType = new MediaTypeHeaderValue("application/vnd.collection+json");
using (var writer = new JsonTextWriter(new StreamWriter(_memoryStream)){CloseOutput = false})
{
//var readDocument = new ReadDocument(); {IReadDocument.Collection = collection};
var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(writer,collection);
writer.Flush();
}
_memoryStream.Position = 0;
}
Although above code compiles and to some extent sorts out the problem but then again I will have another problem of not being able to consume the JsonCollection content in my controller unit tests. Consider the following unit test code snippet:
using (var request = CreateRequest())
{
var controller = new TestController(DataService) {Request = request};
var temp = await controller.ListAsync(gridSearchData, sampleSearchData);
if ((temp is NotFoundResult) && (sampleCollection.Any()))
{
Assert.Fail("Controller did not return any result but query did");
}
var json = await temp.ExecuteAsync(cancellationTokenSource);
var readDocument = json.Content.ReadAsAsync<ReadDocument>(new[] {new CollectionJsonFormatter()}, cancellationTokenSource).Result;
}
Since I did not set the collection property of ReadDocument readDocument is always empty and I cant read its content.
How do you asynchronously read the contents of JsonCollection on the client side in WEB API projects?
To get a Clear picture of the approach look at the Conference Web Api
and the authors blog
OK all, this has been fixed. The Collection property is now settable again.
I have just pushed release 0.7.0 with this fix, a major naming refactoring as well as a nice improvement to serialization to not write out empty collections.
Please see the release notes for the changes (especially the naming as the package names and namespaces have changed)
As far as I see from your code, you do not serialize a ReadDocument object, but only a property of it (Collection), and then you try to deserialize that value into a new ReadDocument object.
A sample ReadDocument should serialize like this
"{"Collection": [1,2,3,4,5] }"
But you serialize collection, so you get
"[1,2,3,4,5]"
I recommend a surrogate class for serialization like this
class SerializableReadDocument
{
public Collection Collection { get; set; }
}
and update your serialization code like this
using (var writer = new JsonTextWriter(new StreamWriter(_memoryStream)){CloseOutput = false})
{
var readDocument = new SerializableReadDocument() { Collection = collection };
var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(writer, readDocument);
writer.Flush();
}
But, this will not resolve your problem when you try to deserialize your output since ReadDocument does not have a settable Collection property, deserialization will either fail, or return a ReadDocument object with an empty Collection.
You can use SerializableReadDocument if you like in your unit tests.
I am looking into this and will come up with a solution hopeful this weekend, which will either be to make it a public setter, or make the setter internal and have public ctor that accepts a collection.
Sorry for the difficulty.
I'm trying to deserialize an iCal file and then map the collection to a custom POCO. The problem is that I'm not sure where the properties are stored.
Here's the Deserialization method
public static IICalendarCollection Deserialize(String iCalUri)
{
var wc = new WebClient();
var result = wc.DownloadString(iCalUri);
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
var serializer = new iCalendarSerializer();
var collection = (iCalendarCollection)serializer.Deserialize(memoryStream, Encoding.UTF8);
return collection;
}
}
Then in my service, I'm trying to map the properties to my POCO, but I can't find the properties.
var url = string.Format(GoogleICalUrl, calendarID);
var feed = iCalUtility.Deserialize(url);
foreach(var e in feed)
{
calendarModel.title = e.WhereTheHellAreThePropertiesKept;
}
Does anyone know the right way to loop through the iCalendarCollection and get all the properties?
I checked the DDay website but it's been down all day.
After some digging, it turns out that IICalendarCollection has a nested Event collection that needs to be parsed as follows.
var url = string.Format(GoogleICalUrl, calendarID);
var feed = iCalUtility.Deserialize(url);
foreach (var ev in feed.SelectMany(e => e.Events))
{
calendarModel.Add(new CalendarModel {description = ev.Description,
title = ev.Summary});
}