dotnet 6 minimal API circular serialization - c#

I am new to dotnet, trying out dotnet 6 minimal API. I have two models:
namespace Linker.Models
{
class Link : BaseEntity
{
[MaxLength(2048)]
public string Url { get; set;} = default!;
[MaxLength(65536)]
public string? Description { get; set; }
[Required]
public User Owner { get; set; } = default!;
[Required]
public Space Space { get; set; } = default!;
}
}
And:
namespace Linker.Models
{
class Space : BaseEntity
{
public string Name { get; set; } = default!;
public string Code { get; set; } = default!;
public User Owner { get; set; } = default!;
public List<Link> Links { get; set; } = new List<Link>();
}
}
Now when I try to serialize Space model I get error System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. (make sense because Path: $.Links.Space.Links.Space.Links.Space.Links.Space.Links.Space.Links...). Is it posible to prevent dotnet from serializing object this deep? I don't need for dotnet to even try to serialize such a deep relations

You can set ReferenceHandler.Preserve in the JsonSerializerOptions. These docs
How to preserve references and handle or ignore circular references in System.Text.Json discuss further.
For manual serialization/deserialization pass the options to the JsonSerializer:
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.Preserve
};
string serialized = JsonSerializer.Serialize(model, options);
Or to configure globally in minimal API:
using Microsoft.AspNetCore.Http.Json;
var builder = WebApplication.CreateBuilder(args);
// Set the JSON serializer options
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});
You could instead ignore the circular references rather than handling them by using ReferenceHandler.IgnoreCycles. The serializer will set the circular references to null, so there's potential for data loss using this method.

The reason the global configuration is getting ignored is because the wrong JsonOptions is being used. The following should work:
builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
... rest of code
My default for JsonOptions is Microsoft.AspNetCore.Mvc.JsonOptions, which was not the correct JsonOptions object to change and so globally did not to work.

Try adding [JsonIgnore] before the Space declaration as below:
namespace Linker.Models
{
class Link : BaseEntity
{
[MaxLength(2048)]
public string Url { get; set;} = default!;
[MaxLength(65536)]
public string? Description { get; set; }
[Required]
public User Owner { get; set; } = default!;
[JsonIgnore]
[Required]
public Space Space { get; set; } = default!;
}
}

Related

Deserialize JSON having keys with CamelCase and snake_case naming in C#

I am receiving a json file via api, that json file will be converted into a class that I cant touch and has around 400 properties. the json is using for the key names CamelCase and in the same json some keys are in the format of snake_case.
I am currently using System.Text.Json but open to change to Newtonsoft.json is needed.
I tried to create a JsonSnakeCaseNamingPolicy class (only converting the property names to snake_case) and used in the JsonSerializerOptions like this:
var deserializeOptions = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
};
var flexImport = JsonSerializer.Deserialize<List<FlexImport>>(input.MappedObjectJson, deserializeOptions);
But then the properties in CamelCase don't get populated. Any idea on how to achieve this situation?
This the json sample:
[{\"BatchId\":123,\"Title_Id\":123,\"CurrentNumber\":\"aa705128\",\"address\":\"122 BLACKSGATE EN\",\"curr_interest_rate\":4},{\"BatchId\":2,\"Title_Id\":1,\"CurrentNumber\":\"27705128\",\"address\":\"90 ARMA DR\",\"curr_interest_rate\":5},{\"BatchId\":2,\"Title_Id\":2,\"CurrentNumber\":\"30877674\",\"address\":\"6485 N SIN CIR\",\"curr_interest_rate\":4}]"
And here is part of the destination class:
public class FlexImport
{
public long BatchId { get; set; }
public long TitleId { get; set; }
public string CurrentNumber { get; set; }
public string Address { get; set; }
public decimal? CurrInterestRate { get; set; }
}
Use JSON attributes
public class FlexImport
{
public long BatchId { get; set; }
[JsonPropertyName("Title_Id")]
public long TitleId { get; set; }
public string CurrentNumber { get; set; }
[JsonPropertyName("address")]
public string Address { get; set; }
[JsonPropertyName("curr_interest_rate")]
public decimal? CurrInterestRate { get; set; }
}
Etc. Adjust as needed.

Consume Data From this endpoint

I am getting tdata from a certain endpoint and the problem id on serialization to my classes. I want to cast the bellow data to my class but cant get how the class should be structured. Check out the data .....
{
"-LYG_AI_oGYjNBrzMlKF": {
"chatDispayText": "",
"chatId": "-LYG_AI_oGYjNBrzMlKF",
"chatName": "",
"chattype": "single",
"imageUrl": "https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Amazing-celebrities-hd-wallpaper-PIC-WPD004734.jpg",
"lastMessageSent": "aiye",
"lastMessageSentTime": 1549704416263,
"synched": false,
"users": {
"-LYG_AIZ5MvTbjR7DACe": "Uicpm3L15TX0c15pKCI6KUEARyB3",
"-LYG_AI_oGYjNBrzMlKE": "Xsr0z9lsqNOEytX61lJvaGz1A8F2"
}
}
}
If the data you get out the endpoint has a dynamic structure, you can make use of a key-vale pair collection or a dictionary. For instance:
JObject jObject = JObject.Parse(Data); // This would already give you a key-value pair collection
Dictionary<String,Object> collection = new Dictionary<String, Object>();
foreach(var obj in jObject){
collection.Add(obj.Key, obj.Value);
}
However, this isn't a strongly typed approach which means that it is not effective in the majority of scenarios. A better solution when dealing with endpoints would be to define a class with fixed schema, actually something you need in your code, and then map the class to the object yielded by the endpoint using a metadata struct. For example:
public class ChatInfoModel
{
[JsonProperty(Metadata.ChatId)]
public long ChatId { get; set; }
[JsonProperty(Metadata.ChatId, Required = Required.AllowNull)]
public String Message { get; set; }
}
public struct Metadata
{
public const String ChatId = "userChatId";
public const String Message = "messageTxt";
}
And then
var deserializedObject = JsonConvert.DeserializeObject<ChatInfoModel>(data);
However, if your class has the exact same naming convention (but should not necessarily follow the camelCase naming convention) for its properties as in the serialized data, the JsonProperty attribute would not be needed.
You can also deserialize the object without using JsonProperty attribute manually using the first approach, and it is actually advantageous in certain scenarios where your schema comes from a configuration file rather than a struct.
Take inspiration from the Structure below:
public class Rootobject
{
public LYG_AI_Ogyjnbrzmlkf LYG_AI_oGYjNBrzMlKF { get; set; }
}
public class LYG_AI_Ogyjnbrzmlkf
{
public string chatDispayText { get; set; }
public string chatId { get; set; }
public string chatName { get; set; }
public string chattype { get; set; }
public string imageUrl { get; set; }
public string lastMessageSent { get; set; }
public long lastMessageSentTime { get; set; }
public bool synched { get; set; }
public Users users { get; set; }
}
public class Users
{
public string LYG_AIZ5MvTbjR7DACe { get; set; }
public string LYG_AI_oGYjNBrzMlKE { get; set; }
}

Converting infinitely nested objects in .NET Core

EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?

WebAPI not serializing inherited class on POST?

Here is my controller method:
[System.Web.Http.HttpPost]
[System.Web.Http.Route("api/exercise")]
public HttpResponseMessage CreateExercise(ExerciseDto exercise) {
Here are my classes:
public class Exercise {
[Key]
[Required]
public int ExerciseId { get; set; }
[StringLength(300, ErrorMessage = "The value cannot exceed 300 characters. ")]
public string Title { get; set; }
}
[NotMapped]
[Serializable]
public class ExerciseDto : Exercise {
public ExerciseDto(Exercise exercise) {
ExerciseId = exercise.ExerciseId;
Title = exercise.Title;
UserHasExercise = true;
}
public bool UserHasExercise { get; set; }
public List<int> SomeIds { get; set; }
}
If I use type Exercise in the API controller, the object comes through. I created the DTO to extend the POCO with some more properties, but if I use this ExerciseDto class, I get null whenever I send the same data I was sending before. What is happening?
WebAPI Config:
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.None;
config.Formatters.Remove(config.Formatters.XmlFormatter);
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var enumConverter = new Newtonsoft.Json.Converters.StringEnumConverter();
json.SerializerSettings.Converters.Add(enumConverter);
Update:
My solution for the time being is to scrap the idea of DTOs altogether and just extend the POCO with [NotMapped] properties:
public class Exercise {
[Key]
[Required]
public int ExerciseId { get; set; }
[StringLength(300, ErrorMessage = "The value cannot exceed 300 characters. ")]
public string Title { get; set; }
[NotMapped]
public bool UserHasExercise { get; set; }
[NotMapped]
public List<int> SomeIds { get; set; }
}
This keeps everything super simple, but I have a feeling it's not a best practice for more complicated models. Am very interested in seeing the proper way to handle this.
ExerciseDto needs a parameterless constructor in order for the Post body deserialize properly.
Update: your update is the way I would do it.
I believe the problem is that you've declared ExerciseDto as [Serializable] but you're not passing all ExerciseDto model parameters in your request (you mentioned that you pass the same data as you did for Exercise). You can either remove the [Serializable] attribute or, alternatively, add [JsonIgnore] to your model's UserHasExercise and SomeIds parameters. If you're passing the data as xml and not json, then use [IgnoreDataMember] instead of [JsonIgnore].

Silvelight&RIA Services&Entity Framework problems with partial classes and complex types there

I'm using RIA Services with Entity Framework and Silverlight as the client Application. I've got some custom properties for EF Entity through partial class. There is a field in Database with XML type and it's mapped to Entity Framework as string. And I use partial class to deserialize this xml string to real object.
This is the partial class for EF Configuration Entity:
public partial class Configuration
{
private ServiceCredentials _serviceCredentialsObject;
[DataMember]
public ServiceCredentials ServiceCredentialsObject
{
get
{
return this._serviceCredentialsObject
?? (this._serviceCredentialsObject = this.DeserializeServiceCredentialsToObject());
}
set
{
this._serviceCredentialsObject = value;
this.SerializeServiceCredentialsObject();
}
}
public ServiceCredentials DeserializeServiceCredentialsToObject()
{
if (string.IsNullOrEmpty(this.ServiceCredentials))
{
return null;
}
var result = XmlSerializerHelper.Deserialize<ServiceCredentials>(this.ServiceCredentials);
result.FileEncoding = result.FileEncoding ?? Encoding.UTF8;
return result;
}
public void SerializeServiceCredentialsObject()
{
if (this.ServiceCredentialsObject == null)
{
this.ServiceCredentials = null;
return;
}
this.ServiceCredentials = XmlSerializerHelper.Serialize(this.ServiceCredentialsObject);
}
}
And this is the object i'm trying to Deserialize:
[Serializable]
public class ServiceCredentials
{
public NetworkCredential Credential { get; set; }
public Encoding FileEncoding { get; set; }
[XmlIgnore]
public long HistoryID { get; set; }
public string LoadFileStoragePath { get; set; }
public string ManualLoadFilePath { get; set; }
public bool NeedAuthorization { get; set; }
[XmlIgnore]
public string ProviderID { get; set; }
public string SourceUrl { get; set; }
public bool AutomaticTransferToProductive { get; set; }
}
When I'm trying to use Configuration Entity on silverlight client-side with generated code find an issue that there is no ServiceCredentialsObject in Configuration class. And it's not added to DomainService.metadata.cs if i create new one. If i add ServiceCredentialsObject to DomainService.metadata.cs manually i can access it on clien-side after rebuild but i can find only properties with simple types there. For example a can acess HistoryID,SourceUrl,AutomaticTransferToProductive but there are no generated properties for
public NetworkCredential Credential { get; set; }
public Encoding FileEncoding { get; set; }
How can i fix this?
I've found the only way to solve this problem. The solution was just to deserialize xml field from entity framework on client-side. I've created partial class Configuration for generated code in silverlight. I don't know if it's the best solution but it works.

Categories