Customize serialization to JSON to leave some fields - c#

I have a .NET Core API project backed by MongoDB.
I have a sample model class:
public class MyModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("aField")]
public string AField { get; set; }
[BsonElement("hiddenField")]
public string HiddenField { get; set; }
}
What I want to achieve is to set the HiddenField to null every time when it is returned as the payload in my controller. I am thinking of a custom JSON serializer but don't know how to do it. Can anyone help?
NB: I don't want to manually set the HiddenField to null in every single controller or action.

Apply JsonIgnore attribute to HiddenField property.
public class MyModel
{
// ... other members
[JsonIgnore]
[BsonElement("hiddenField")]
public string HiddenField { get; set; }
}
Since under the hood ASP.Net Core uses Newtonsoft.Json (aka Json.NET) library to de/serialize JSON payloads, you can control serialization and deserialization with the attributes from that library.
Note that JsonIgnore attribute works in both directions: if a client sends hiddenField to the server, the controller won't populate it in the model.
It also worth noting that starting with ASP.NET Core 3.0, Json.NET won't necessarily be the default mechanism of JSON serialization (see this announcement): in the 3.x versions of ASP.NET Core, one must ensure that integration with Json.NET is plugged in, for current solution to work.
Regarding the architectural discussion in the OP comments, it all depends. Of course, mixing such attributes as JsonIgnore and BsonElement in one class means coupling of two separate concerns: service API and data persistence. However, in small and simple applications, proper separation of concerns might be an overkill, and the most straightforward approach might be a better fit.

I find it easier to create a ViewModel (I tend to use ViewModel as a definition of any model I expose, either by sending it to a server side presentation layer or any external resource REST/etc via JSON/XML/etc).
In this instance, I'd create:
public class MyModelVM
{
public string Id { get; set; }
public string AField { get; set; }
public string HiddenField { get; }
}
HiddenField has no set, so it will always be null. Then set your serialization options and return your JSON.
var myModelVM = new MyModelVM(); // however you want to create/map the values
var json = JsonConvert.SerializeObject(myModelVM,
Newtonsoft.Json.Formatting.None,
new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Include
});
If you didn't map values to MyModelVM, then the resulting JSON would look like:
{
"Id" : null,
"AField" : null,
"HiddenField" : null,
}

Related

Parsing Json array to C# Object

I'm building a .NET WebAPI that receives Json through a Post operation. The Json that's being received could look like the following:
{
"site": "00131231201d010231",
"publishTime": 123123123123,
"domains": [
"example.com"
],
"publishedBy": {
"name": "John Doe",
"id": "00211231201d010231"
}
}
I converted my Json response type to C# objects which look like the following:
public class Project
{
[Key]
[JsonPropertyName("site")]
public string Site { get; set; }
[JsonPropertyName("publishTime")]
public long PublishTime { get; set; }
[JsonPropertyName("domains")]
public List<Domain> Domains { get; set; }
[JsonPropertyName("publishedBy")]
public PublishedBy PublishedBy { get; set; }
}
public class PublishedBy
{
[JsonPropertyName("name")]
public string Name { get; set; }
[Key]
[JsonPropertyName("id")]
public string Id { get; set; }
}
public class Domain
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
}
As you can see, my goal is to add the contents to my database. Only when I use List Domains, it gives me an error saying I can't use strings in EFCore when I try to add a migration.
So, I created an object called Domain. But now when I try to deserialize it gives me the following error:
System.Text.Json.JsonException: The JSON value could not be converted to spine_management.Models.Domain.
Does anyone happen to know what type I should make Domains and/or what the best way to deserialize this object is?
EDIT:
I want to keep the domains attribute, I don't want to ignore or delete them.
It's not uncommon to split between two different model structures for interacting with different infrastructure points. In this case your infrastructure points are:
Deserializing JSON input
Persisting data with EF
You can treat the JSON input like a "view model". It's not your core model which maps to EF database entities, but rather just an anemic DTO for deserializing data. For that model, Domains is simply a list of strings:
[JsonPropertyName("domains")]
public List<string> Domains { get; set; }
This view model is local to the application layer, not part of the core domain. Within the application logic, after deserializing the input, you can map it to the domain object. That's where you would translate the list of simple strings into a list of Domain objects. (And translate back in any output operations.)
As long as the mapping logic (which might be made simple by using tools like AutoMapper, though in this case the logic is pretty straightforward and doesn't really necessitate adding more tools) is encapsulated within that application layer, it won't pollute the rest of the domain logic.
Though it may certainly be possible to configure one or both of these tools to work together more smoothly, I often find that a simple translation layer between dependency-specific DTOs and core domain models is much simpler to build and maintain.
I currently use Newtonsoft JSON for serialize and deserializing. I think the reason for this error is you wrote named the string in domain "Name", but it has to be "name". Hope it works!

WebApi Controller model serialization issue

I'm facing little stranger issue with Web API controller. I have a collection which is being passed in an action of api controller. Object being used is collection is having 4 properties.
My action is able to accept collection parameter when it's properties are in specific order. See below :-
[HttpPost]
public ForexRates UpdateRates([FromBody] Rates rates)
{
// TODO: Obviously code :)
return rates;
}
This code is being place in API controller & calling from Postman. See below:-
<rates>
<rate>
<id>fefef</id>
<rate>35353.333</rate>
<series>dfefge</series>
<series-order>sfefefef</series-order>
</rate></rates>
If I change the order of the properties I started getting null value in my action. Can some one please explain this :)
Models
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Rate { get; set; }
}
public class Rates : Collection<ForexRate>
{
}
You will need to control the order with which your XML is serialized. Use XmlElementAttribute and specify the Order.
There is a similar question here
FYI, I suppose there is no way for you to change the order of the properties, while you supply from PostMan to your WebApi service. You will need to follow the exact order.
If you don't wanna do that, then pass this Xml as a string parameter and then parse it inside a method.
The default binder can have issues when the same name is used in different places during binding.
In your case you've got Rate.Rate - both class name and property name. Try changing your class to (and corresponding xml for the post) :
public class Rate
{
public string Id { get; set; }
public string Date { get; set; }
public double Value { get; set; }
}
and then try changing the order.
While I don't have a definitive reason why it works in one order and not another, it's likely that when it gets to the Rate(double) value it tries to create a new Rate(object) but doesn't have the correct properties (as its just a double).
A more complicated solution would be to write a specific model binder for the Rate object.
The issue has to do with the DataContractSerializer which expects the elements to occur in a specific order (alphabetical with some consideration given to inheritance). That's the default serializer used when creating a Web API project.
You can override this and specify a different serializer during API Configuration like this:
GlobalConfiguration.Configuration.Formatters.XmlFormatter
.SetSerializer<SomeType>(new XmlSerializer(typeof(SomeType)));

Serialize embedded class / object independently or as string?

I am implementing a mongodb cache for this asp.net webapi output cache (I agree redis would be better / faster but for now, I need a mongodb implementation!)
Anyway,
I have a CachedItem class that holds my key / value:
[BsonIgnoreExtraElements]
public class CachedItem
{
[BsonElement("key")]
public string Key { get; set; }
[BsonElement("value")]
public object Value { get; set; }
}
Value is an object, that could be anything, we don't control that.
In one of my tests, I have a very simple poco:
public class UserFixture
{
public UserFixture()
{
Id = Guid.NewGuid();
}
public Guid Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
When this is set to the Value it is serialized and persisted.
When I try to retrieve it, it fails to deserialize, as it has automatically grabbed the "Id" property.
An error occurred while deserializing the Id property of class WebAPI.OutputCache.MongoDb.Tests.UserFixture: Cannot deserialize Guid from BsonType ObjectId
Obviously, I can't decorate UserFixture
Is there any way I can tell MongoDB driver to basically serialize CachedItem.Value, as say, a string?
I could use JSON.net to do this before saving, and deserialize it on the way back out, but I was hoping to avoid this.
It's also on GitHub
That link should take you straight to the relevent commit if you'd like to try the failing test.
You can of course tell MongoDB to serialize your class as a string by building your own custom BsonSerializer. I have found it easier to inherit from their BsonStringSerializer. You also need to register that serializer with your specific type. (I suggest using a BsonSerializationProvider for that)
What you do need to think about is how to represent all your possible data as a string so you could deserialize it back to your application (Consider for example that you probably need to save the type information).

Serializer ignores properties that use another serialized property

I am working on an endpoint in asp.net that serializes and returns some data, using the default serializer.
The consuming applications are transitioning between changing names for properties (in other words, some existing applications are using names like ...Vat; while newer ones are using ...Tax. I therefore need to keep both names in the response for the moment, until these changes are complete.
The return type is IList.
public class Product
{
...
public decimal PriceIncVat { get; set; }
public decimal PriceIncTax { get { return PriceIncVat; } }
public int TaxCode { get; set; }
...
}
However, when I examine the response in fiddler, only the PriceIncVat property exists in the json list of products.
I can't think of any reason why the above wouldn't work. I added TaxCode at the same time as PriceIncTax, and it is returned, so I know the code of the endpoint is up to date.
And on the client side of a newer client project we have:
public class ProductDto
{
...
public decimal PriceIncTax { get; set; }
public string TaxCode { get; set; }
...
}
Very confused here.
The serializer assumes you will need to deserialize the data some time. Hence by default only properties with a getter and a setter are considered.
When using the DataContractJsonSerializer, it's possible to turn on serialization of read-only properties using the SerializeReadOnlyTypes property (despite its rather misleading name).
Side note: Check-out the Json.NET serializer, which gives more options and better control over the (de)serialization process.

How to POST in ASP.NET Web API with model property as interface

Suppose I have a model:
public class Menu
{
public string Name { get; set; }
public IMenuCommand Next { get; set; }
}
IMenuCommand could have different implementations, like:
public class NextStepCommand : IMenuCommand
{
public int Step { get; set; }
}
public class VoiceCommand : IMenuCommand
{
public string Message { get; set; }
}
And I want to POST menus with different commands to the ASP.NET Web API service. How can I do that?
The request below will create an object with specified Name, but Next command will be null:
POST http://localhost/api/menus: {"name":"bob","next":{"step":1}}
Returns 201: {"Name":"bob","Next":null}
Default Web API binders can't map my request params to the needed C# type - of course it's a tricky part. Can I use some "known-type" attribute for interface-based properties or is there any other approach to handle this case, probably a custom model binder?
I think what you're looking for is Json.NET's support for type name handling. It allows you to specify the type to deserialize into by adding the "$type" json tag. You can try this code out to see how it works:
Console.WriteLine(JsonConvert.DeserializeObject<Menu>(
#"{
""name"":""bob"",
""next"":
{
""$type"" : ""ConsoleApplication.NextStepCommand,ConsoleApplication"",
""step"" : 1
}
}",
new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto }).Next);
You'll have to replace the namespace and assembly name with your own, but you should see the NextStepCommand being correctly deserialized.
In WebAPI, you'll need to tweak your request to add the "$type" type information, and you'll need to enable TypeNameHandling like this:
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;

Categories

Resources