I am adding an API to an existing database so I am stuck with the current MongoDB document schema. The document is flat with no nested objects. I want to deserialize a group of the fields into a child object. I was wondering if there was a way of doing that with the BsonElement attribute? Another approach I came across was using a custom serializer. Would it be possible to write a custom serializer for the child object even though there is no field matching the child object's name? Or would I need to write the custom serializer at using the parent object?
Here is an example of a document:
{
"Name": "Name",
"AddressLine1": "321 street",
"AddressLine2": "apt 1",
}
Here is an example of the C# classes and what I was trying to do:
public class Customer
{
[BsonElement("Name")]
public string CustomerName {get; set;}
public Address Address {get; set;}
}
public class Address
{
[BsonElement("AddressLine1")]
public string AddressLine1 {get; set;}
[BsonElement("AddressLine2")]
public string AddressLine2 {get; set;}
}
There's no way to use attributes to achieve what you are wanting, this because the attributes apply given conditions to a BsonMemberMap which resides in the BsonClassMap.
As you said this however can be achieved by creating a custom serializer.
public class CustomBsonSerializer : IBsonSerializer<MyType>
{
}
BsonSerializer.RegisterSerializer(new CustomBsonSerializer())
However, if you want to write a bespoke serializer it can become a bit brittle.
I'd personally opt for creating 2 sets of models, one to model the current state of your documents in MongoDB and then another model to create the structure that you want to use.
Related
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!
I am trying to deserialize JSON that i receive from a webservice. The JSON looks like that:
{ "data":
[
{"name": "john", "company": "microsoft"},
{"name": "karl", "company":"google"}
]
}
My model which i want to deserialize into:
public class employee {
public string name {get; set;}
public string company {get; set;}
}
The problem is, that i cannot deserialize using System.Text.Json because of that object name "data". How can i make the deserializer to unwrap / ignore the data tag and just start from whatever is inside that tag?
Just create a wrapper object and use it for deserialization:
public class Root {
public List<employee> data {get; set;}
}
var employees = JsonSerializer.Deserialize<Root>(jsonString).data;
In case there a lot of different types contains this pattern you can make Root generic:
public class Root<T> {
public List<T> data {get; set;}
}
var employees = JsonSerializer.Deserialize<Root<employee>>(jsonString).data;
Note that data contains a collection of employees not a single record.
Also note that you can use recommended Pascal casing for property names, deserializer should be able to pick it up (of it it does not - you can help it by providing JsonSerializerOptions with correct PropertyNamingPolicy set).
I just stumbled over this - I registered my Records which include some ImmutableLists.
Basically, like this:
record A {
[BsonId]
string Id {get; init;}
string Name {get; init;}
ImmutableList<string> Properties {get; init;}
};
record B {
[BsonId]
string Id {get; init;}
string Name {get; init;}
ImmutableList<A> Members {get; init;}
}
Now, serializing those to my MongoDB works fine, everything looks like it should. However, the deserialization back in to the Records does not work, from the exception thrown it seems that the Add method does not quite work the way the deserializer expects (well, sure, instead of adding to the list itself it instead returns a new list with the added item).
Now, I found how to write a basic deserializer myself - but the nested A in B makes it a bit painful and it's quite brittle (e.g. if I later add additional nullable fields). What would be the best way to approach this problem?
This improvement raised with MongoDB explains the behavior and a possible solution by creating a custom serializer just for ImmutableList<T>:
public class ImmutableListSerializer<TValue> :
EnumerableInterfaceImplementerSerializerBase<ImmutableList<TValue>, TValue>
{
protected override object CreateAccumulator() =>
ImmutableList.CreateBuilder<TValue>();
protected override ImmutableList<TValue> FinalizeResult(object accumulator) =>
((ImmutableList<TValue>.Builder)accumulator).ToImmutable();
}
You can register this serializer for the required properties either on the properties themselves:
[BsonSerializer(typeof(ImmutableListSerializer<string>))]
public ImmutableList<string> Properties { get; init; }
// ...
[BsonSerializer(typeof(ImmutableListSerializer<A>))]
public ImmutableList<A> Members { get; init; }
Alternatively, you can register it globally when initializing the application:
BsonSerializer.RegisterSerializer(
typeof(ImmutableList<A>),
new ImmutableListSerializer<A>());
BsonSerializer.RegisterSerializer(
typeof(ImmutableList<string>),
new ImmutableListSerializer<string>());
The downside is that you need to do this for each list type you need, but there is hope that the driver will support this sooner or later.
I have an object, Project, that contains many fields, some complex some not. It is an EF class, so I can't edit it to add attributes.
I just want to generate a JSON object containing 2 of the fields (one int (id) and one string (name))
I'd hate to create another ViewModel just for this...
In my viewmodel I have a List<Project>. Is there a way to use HTML helpers to get a JSON representation of only the properties I choose without using attributes?
Here is an example of the Project class:
public class Project
{
public int Id {get; set; } <-- Serialize this
public string Name { get; set; } <-- Serialize this
public Object AnotherObject [ Get; Set; } <-- Ignore this
....
}
I'd like it to become:
[{"id":"27","name":"test1"},{"id":"34","name":"test2"},{"id":"35","name":"test3"}]
The ultimate goal here to is output the json directly to the view as a var so that it can be used in building a JsGrid.
If there is a way to do it with Html helpers, that would be great.
Thanks!
Json.NET has a great built in ignore feature. If you tag the Property you want to exclude with the [JsonIgnore] attribute, the serializer will not serialize that property.
[JsonIgnore]
public bool IsValid { get; set; }
I am having a hard time finding good detail on NEST 2.0, the wrapper for Elasticsearch 2.2 I am using. My question is this: Can I do a bulk insert on an object (class with public data members) and map that to Elasticsearch where only the mapped fields between my C# class and the ES server mapping will save? And it will not add the additional fields in my class I do not want?
Right now I have a class of with strings and doubles and lists of other classes in it. I want to save the strings and doubles, but NOT include the Lists on my bulk inserts to Elasticsearch. It wants to save every piece of data in every field of my class. Is there a class member attribute or some other way to say "do not add this field if it has no mapping" that I have missed? I hope so.
You can ignore properties of your POCO in a number of ways with NEST 2.x. Let's use the following POCO as an example
using Nest;
using Newtonsoft.Json;
[ElasticsearchType(Name = "company")]
public class Company
{
public string Name { get; set; }
[String(Ignore = true)]
public string IgnoreViaAttribute { get; set; }
public string IgnoreViaSettings { get;set; }
[JsonIgnore]
public string IgnoreViaSerializerSpecificAttribute { get; set; }
}
1.Using the Ignore property on a derived ElasticsearchPropertyAttribute type (in our example, StringAttribute on IgnoreViaAttribute property) applied to the property that should be ignored on the POCO
2.Using the .InferMappingFor<TDocument>(Func<ClrTypeMappingDescriptor<TDocument>, IClrTypeMapping<TDocument>> selector) on the connection settings
var settings = new ConnectionSettings(new Uri("http://localhost:9200"))
.InferMappingFor<Company>(i => i
.Ignore(p => p.IgnoreViaSettings)
);
var client = new ElasticClient(settings);
These settings are cached per instance of ConnectionSettings.
3.Using an ignore attribute applied to the POCO property that is understood by the IElasticsearchSerializer used, and inspected inside of the CreatePropertyMapping() on the serializer. In the case of the default JsonNetSerializer, this is the Json.NET JsonIgnoreAttribute. In our example, this is demonstrated by the attribute applied to the IgnoreViaSerializerSpecificAttribute property.
What I found by digging around a bit and testing a small class is that the following structure did indeed hide the attributes when you post a class with NEST 2.0 C#. If you do this just above the class member you wish to ignore when doing a bulk add that covers it.
[String(Ignore = true)]