How to add a property to a System.Text.Json.JsonDocument - c#

I was accustomed to using Newtonsoft's JObject in my Asp.net web api's. I could add properties at will using:
JObject x = new JObject() { "myInt", 25 };
//or
x.Add("myInt", 25);
I ran into an error when trying to return a JObject from a ASP.Net core 3.1 web app.
System.NotSupportedException: The collection type 'Newtonsoft.Json.Linq.JObject' is not supported.
So NewtonSoft.Json is not supported in .Net core 3.1. So I looked at this article https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to but it seems that you can not add a property to a JsonDocument. This seems very odd and I thought I might be missing some information. So is there a way to add a property and value to a JsonDocument?

Related

Why is the Newtonsoft Json serializer ignoring the [JsonIgnore] attribute? [duplicate]

I have this setup
Asp Core 3.1 API
Shared Lib with MyClass that is sent between API and client
Client App with Com classes
On the MyClass that is sent between them I have a field ComField that references a com class, this is only used on the client app and should not be (de)serialized, therefore I have it marked with [JsonIgnore]
class MyClass{
[JsonIgnore]
public ComThingy ComField {
get{// code here that throws the error when deserilaized on the API}
set{// code here}
}
}
When I write the API to accept the class like this, I get an error when the class is deserialized. The debugger throws the error while deserializing the MyClass, before it enters the method:
[HttpPost]
public async Task<ActionResult<MyClassReply>> Post([FromBody] MyClass myclass){
// code here
}
The API throws an exception that accessing the getter on MyClass throws an error (because that Com stuff isn't on the API).
If I deserialize manually it works fine, but then my swagger doesn't generate the whole API correctly.
[HttpPost]
public async Task<ActionResult<MyClassReply>> Post(){
// this works fine
var rdr = new StreamReader(Request.Body);
var mcj = await rdr.ReadToEndAsync();
var myclass = Newtonsoft.Json.JsonConvert.DeserializeObject<MyClass>(mcj);
// code here
}
So my question is: how come the ASP API builtin deserialization ignores the JsonIgnore attribute and still tries to deal with that property (throwing an error), and why does deserializing manually work as expected (ie ignore that property)? The default pipeline still uses NewtonSoft rght?
And how do I make the default deserialization work correctly?
Starting from ASP.NET Core 3.0, the default JSON serializer is System.Text.Json, and not Newtonsoft.Json. You need to call .AddNewtonsoftJson() in your Startup.cs to use it (see for example this answer).
Your issue might simply be that you're not using the proper JsonIgnore attribute. Both serializers have the same named attribute:
System.Text.Json.Serialization.JsonIgnoreAttribute
Newtonsoft.Json.JsonIgnoreAttribute
Maybe your using statement are importing the Newtonsoft.Json one instead of the System.Text.Json one?

System Text JsonSerializer Deserialization of TimeSpan

In researching how to deserialize a TimeSpan using Newtonsoft's JSON.net I came across code in my current project that did not use Json.net. It used System.Text.Json.JsonSerializer and appeared to not fail on the operation of deserializing the TimeSpan property, as per the unit tests I was running.
Great I thought, .Net Core 3.1 has surpassed the historical issue of deserializing a TimeSpan and all is good. So fired up a test case in the latest version of Linqpad 6 (which uses .NET Core) to verify and to my chagrin it failed.
So the question is, can the TimeSpan be serialized/deserialized using either library (and if so how)… or is my test case below flawed in some respect?
Code
public class Project { public TimeSpan AverageScanTime { get; set; } }
Linqpad C# Code
var newP = new Project() { AverageScanTime = TimeSpan.FromHours(1) };
newP.Dump("New one");
var json = System.Text.Json.JsonSerializer.Serialize(newP);
json.Dump("JSON serialized");
System.Text.Json.JsonSerializer.Deserialize<Project>(json)
.Dump("JSON Deserialize");
Deserialize Failure
JsonSerializer for TimeSpan seem will added in Future (removed from .NET 6 milestone). You can trace this issue in Future milestone or this issue.
At this time, you can implement JsonTimeSpanConverter on your own. Or you can install Macross.Json.Extensions nuget package and follow the instruction to de/serializer.
An addition to the answer from Poy Chang
Swagger (Swashbuckle) also requires a configuration
services.AddSwaggerGen(options =>
{
options.MapType(typeof(TimeSpan), () => new OpenApiSchema
{
Type = "string",
Example = new OpenApiString("00:00:00")
});
});
TimeSpanConverter is available in .NET 6.0. So TimeSpan serialization/deserialization will work without custom converters out of the box.
Issue: https://github.com/dotnet/runtime/issues/29932
Implementation: https://github.com/dotnet/runtime/pull/54186

How can I change Serialization approach (Typed,None) for some actions in Asp.Net Core?

I have asp.net core 2.2 web Application. The application contains Command and Query Actions, Command Actions must return typed json response:
{
$type:"A.B.Customer, x.dll ",
id: 11231,
name: "Erwin .."
}
Query Actions must return non typed response:
{
id: 34234,
name: "Erwin .. "
}
We can choose one outputformatter for Json responses at startup.
services.AddMvc().AddJsonOptions(o =>
{
SerializerSettings.TypeNameHandling=Newtonsoft.Json.TypeNameHandling.Objects
// SerializerSettings.TypeNameHandling = Newtonsoft.Json.TypeNameHandling.None
}
So How can I change output formatter for same response type (application/json) by action?
There are multiple ways to do that.
One would be to directly call JSON.NET Methods and pass your settings to it.
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
return base.Content(JsonConvert.SerializeObject(query, settings), "application/json");
Alternatively, return a JsonResult
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
return new JsonResult(query, settings);
Be careful with the second example. In .NET Core 3.0, JSON.NET isn't hard-wired into ASP.NET Core anymore and ASP.NET Core ships w/o JSON.NET and uses the new System.Text.Json classes base don span.
In ASP.NET Core 2.x, JsonResult accepts JsonSerializerSettings as second parameter.
From ASP.NET Core 3.x, JsonResult accepts object and depending on the serializer used, a different type is expected.
This means, if you use the second example in your code, it will (at first) break, when migrating to ASP.NET Core 3.0, since it has no dependencies on JSON.NET anymore. You can easily add JSON.NET Back to it by adding the Newtonsoft.Json package to your project and add
servics.AddNewtonsoftJson();
in your ConfigureServices methods, to use JSON.NET again. However, if in future, you ever decide to move away from JSON.NET and use System.Text.Json or any other Json serializer, you have to change all places where that's used.
Feel free to change that to an extension method, create your own action result class inheriting from JsonResult etc.
public class TypelessJsonResult : JsonResult
{
public TypelessJsonResult(object value) : base(value)
{
SerializerSettings.TypeNameHandling = TypeNameHandling.None;
}
}
and reduce your controller's action code to
return new TypelessJsonResult(query);

Unable to resolve type: System.String, System.Private.CoreLib, Version 4.0.0.0 - Asp.net Core (.net core 2.1) server, .NET 4.6.1 Client

I've got a legacy client application written in .net 4.6.1 consuming data of a WCF server.
I am trying to replace the WCF server with an ASP.NET Core application and Protobuf serialization.
I am using libraries:
Protobuf-net in both the client and server for serialization/de-serialization
Core WebApi Formatters in the server
Trying in the client side to deserialize the returned content like below:
var resultData = ProtoBuf.Serializer.Deserialize<ExcelDropdownNode>(response.Content.ReadAsStreamAsync().Result);
But I am getting an error in the protobuf library:
Unable to resolve type: System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)
Any idea what could I be doing wrong?
As a side note, I have to return dynamic data in the server and protobuf isn't very happy about it. I have marked a List<object> property with DynamicType=True and changing all ValueTypes to string (i.e 4 -> "4"). This allowed the serialization on the server-side to work.
The dynamic property in the ExcelDropDownCode is as below:
[ProtoMember(1, DynamicType = true)]
public List<object> DataItems
{
get { return dataItems; }
set { dataItems = value; }
}
Managed to solve by adding in the client side the type resolution code below:
RuntimeTypeModel.Default.DynamicTypeFormatting += (sender, args) => {
if (args.FormattedName.Contains("System.String, System.Private.CoreLib"))
{
args.Type = typeof(string);
}};

Add to odata collection property in .NET

Using the odata-v4 service connection in visual studio C# .NET, I get an entity of type testDefinition. testDefinition has property called features which is a collection of entities of type feature.
In the DB, testDefinition to feature is many to many with a junction table.
In my code, I add a service reference to a web service serving the EDMX of the DB.
Code gets generated correctly and I run:
var dsc = new Container(new Uri("http://webserver/webapi/odata/"));
var someFeature = new Feature
{
name = $"Sample feature created with C# {DateTime.UtcNow}",
};
var someOtherFeature = new Feature
{
name = $"Sample other feature created with C# {DateTime.UtcNow}",
};
dsc.AddToFeature(someFeature);
dsc.AddToFeature(someOtherFeature);
dsc.SaveChanges();
var someTestDefinition = new TestDefinition
{
name = $"Sample test created with C# {DateTime.UtcNow}",
description = $"A nice succinct description",
};
dsc.AddToTestDefinition(someTestDefinition);
dsc.SaveChanges();
someTestDefinition.features.Add(someFeature);
someTestDefinition.features.Add(someOtherFeature);
dsc.SaveChanges();
The problem is the mapping from the test definition to features is not recorded in the database.
Has anyone encountered this issue, or better yet, resolved it?
For Reference:
I can't say when this was implemented but it works in later versions, the earliest verison I can confirm this against is OData.Client v 7.6.2
If this is an issue for any new users on current versions of the OData Client and Server libraries it generally indicates that the schema is not correctly configured or published.
Also check that the API uses a similarly current OData runtime, the above test was performed against API with Microsoft.AspNet.OData v7.3.0

Categories

Resources