Self Reference when serializing nested Object - c#

I want to store a snapshot of a nested model in my database as sort of a change history.
Therefore I made a model that serializes the whole object into a JSON string for easier storage.
Simplified Data class I want to store:
public class Data
{
public int Id { get; set; }
public string SomeInfo { get; set; }
public virtual ICollection<DataObject> DataObject { get; set; }
}
The DataObject for the collection inside Data:
public class DataObject
{
public int Id { get; set; }
public string SomeMoreInfo { get; set; }
public int DataId { get; set; }
public virtual Data Data { get; set; }
}
My snapshot class looks something like this:
public class DataHistory
{
public int Id { get; set; }
private string _Data;
[NotMapped]
public Data Data
{
get { return _Data == null ? null : JsonConvert.DeserializeObject<Data>(_Data); }
set { _Data = JsonConvert.SerializeObject(value , Formatting.Indented,
new JsonSerializerSettings {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
});
}
}
}
Inside my controller I do:
var data = await _repo.GetData(id);
var historyEntry = new DataHistory();
historyEntry.Data= data;
_repo.Add(historyEntry);
GetData() method inside the repository:
public async Task<Data> GetData(int id)
{
return await _context.Data
.Include(d => d.DataObject)
.FirstOrDefaultAsync(d => d.Id == id);
}
The problem is when I try to serialize one Data entry I get a self reference inside the DataObject so it includes the Data object again and also the DataObjects. Even with ReferenceLoopHandling.Ignore
the produced JSON looks something like this:
{
"Id": 1051,
"SomeInfo": "asdasd",
"DataObject": [
{
"Id": 121,
"SomeMoreInfo": "asdasd",
"Data": {
"Id": 1051,
"SomeInfo": "asdasd",
"DataObject": [
{
"Id": 122,
"SomeMoreInfo": "asdasd",
"DataId": 1051
}
]
}
},
{
"Id": 122,
"SomeMoreInfo": "asdasd",
"Data": {
"Id": 1051,
"SomeInfo": "asdasd",
"DataObject": [
{
"Id": 121,
"SomeMoreInfo": "asdasd",
"DataId": 1051
}
]
}
}
]
}
EDIT: Expected output would be something like this:
{
"Id": 1051,
"SomeInfo": "Data",
"DataObject": [
{
"Id": 121,
"SomeMoreInfo": "DataObject1"
"DataId": 1051
},
{
"Id": 122,
"SomeMoreInfo": "DataObject2"
"DataId": 1051
}
]
}
How can I stop it from including Data a second time without using DTOs?
EDIT:
If I try it without Entity Framework, ReferenceLoopHandling.None works as expected. See Dotnet Fiddle https://dotnetfiddle.net/bmAoAW.
So there seems to be a problem with my EF Core configuration or something.

You said in the comments that effectively you want to DataObject.Data property to be ignored whenever you are serializing Data from within DataHistory. You can do this by using a custom ContractResolver to ignore the property programmatically.
Here is the code you would need for the resolver:
public class CustomResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty prop = base.CreateProperty(member, memberSerialization);
if (prop.DeclaringType == typeof(DataObject) && prop.PropertyName == nameof(DataObject.Data))
{
prop.Ignored = true;
}
return prop;
}
}
Then apply it within the JsonSerializerSettings in DataHistory.Data:
set { _Data = JsonConvert.SerializeObject(value , Formatting.Indented,
new JsonSerializerSettings {
ContractResolver = new CustomResolver(),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.None
});
}

Related

C# Json.NET Camel Case Complex Object

I'm trying to serialize an object as camelCase and I'm finding that only the objects at the root level are correctly camelCased. Everything below the root level is coming as PascalCase. I'm writing a console application using Json.NET 12.0.3 and .NET Framework 4.0.
public class Deal
{
public string Id { get; set; }
public string Name { get; set; }
public List<PublicId> PublicIds { get; set; }
}
public class PublicId
{
public string IdType { get; set; }
public string Value { get; set; }
}
To serialize I'm using:
var json = JsonConvert.SerializeObject(deal, Formatting.Indented, new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd",
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
The result json looks like:
{
"id": null,
"name": "Deal 1",
"publicIds": [
{
"IdType": "ID1",
"Value": "A12"
},
{
"IdType": "ID2",
"Value": "B12"
}
]
}
As you can see the values for IdType and Value are not correctly serialied in camelCase but rather are serialized in PascalCase. There are situations where I'll need to change the serialization between the default PascalCase and camelCase so I won't be able to use JsonProperty annotations to force camelCase during serialization. Additionally I'd like to stick to Json.NET as the only json library for my project.
Is there a clean way to get what I'm looking for?
Firstly I noticed that your Deal class does not initialize PublicIds. In that case the code should actually throw an error.
However, your code that specifies the contract solver CamelCasePropertyNamesContractResolver is correct.
The following is a console code that seems to return proper JSON value.
public class Deal
{
public Deal()
{
PublicIds = new List<PublicId>();
}
public string Id { get; set; }
public string Name { get; set; }
public List<PublicId> PublicIds { get; set; }
}
public class PublicId
{
public string IdType { get; set; }
public string Value { get; set; }
}
Then in the console if the try the code below it seems to yield the desired result.
public static void Main()
{
var deal = new Deal();
deal.Name = "Deal 1";
deal.PublicIds.Add(new PublicId { IdType = "ID1", Value = "A12" });
deal.PublicIds.Add(new PublicId { IdType= "ID2", Value= "B12"});
var json = JsonConvert.SerializeObject(deal, Formatting.Indented, new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd",
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
Console.WriteLine(json);
Console.ReadLine();
}
Output by the code:
{
"id": null,
"name": "Deal 1",
"publicIds": [
{
"idType": "ID1",
"value": "A12"
},
{
"idType": "ID2",
"value": "B12"
}
]
}

Create Json using custom attributes in c# using newtonsoft json.net

I would like to create a custom Json object from C# POCO classes, the structure of Json object would be derived from custom attributes applied onto the classes, below is an example of what I am trying to achieve.
Below are few sample classes
public class SomeBaseClass
{
public int BaseId { get; set; }
public string SomeBaseProperty { get; set; }
}
public class NestedClass
{
public string SomeNestedProp1 { get; set; }
public string SomeNestedProp2 { get; set; }
}
[ModelType(TargetModule.COMPLAINT, ModelAffinity.PARENT)]
public class ChildOne : SomeBaseClass
{
public ChildOne()
{
NestedClasses = new List<NestedClass>();
nestedClassesAgain = new List<NestedClass>();
}
public string SomeProperty { get; set; }
public string SomeProperty1 { get; set; }
public string SomeProperty2 { get; set; }
[ModelType(TargetModule.COMPLAINT, ModelAffinity.NESTED)]
public IList<NestedClass> NestedClasses { get; set; }
public IList<NestedClass> nestedClassesAgain { get; set; }
}
Below is a sample custom attribute which is used above.
[Serializable]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public sealed class ModelType : Attribute
{
public ModelType(TargetModule targetModule, ModelAffinity modelAffinity)
{
TargetModuleName = targetModule;
ModelAffinityType = modelAffinity;
}
public TargetModule TargetModuleName { get; }
public ModelAffinity ModelAffinityType { get; }
}
public enum TargetModule
{
COMPLAINT,
PAYMENT,
RECEIPTS
}
public enum ModelAffinity
{
PARENT,
NESTED,
}
Below is the Json object I plan to create using the custom attributes.
{
"complaint": {
"someproperty": "sample value",
"someproperty1": "sample value1",
"someproperty2": "sample value2",
"baseid": "123",
"somebaseproperty": "sample value3"
"nested": [{
"somenestedprop1": "sample nested value1",
"somenestedprop2": "sample nested value2"
}
],
"nestedclassesagain": [{
"somenestedprop1": "sample nested again value1",
"somenestedprop2": "sample nested again value2"
}]
}
}
In the above output the properties / classes containing custom attribute are converted into their values i.e. the property "NestedClasses" is converted into the attribute value "nested", if the other property "NestedClassesAgain" contained the same attribute then the values / properties would get merged into the "nested" Json Array object.
Something like below
"nested": [{
"somenestedprop1": "sample nested value1",
"somenestedprop2": "sample nested value2"
},
{
"somenestedprop1": "sample nested again value1",
"somenestedprop2": "sample nested again value2"
}
]
Tried achieving this using custom ContractResolver as below
public class AttributeContractResolver : DefaultContractResolver
{
private readonly Dictionary<string, string> configDerivedList;
public AttributeContractResolver()
{
configDerivedList = new Dictionary<string, string>
{
{ "ChildOne", "BaseId,SomeProperty,SomeProperty1,NestedClasses,nestedClassesAgain" },
{ "ChildTwo", "BaseId,SomeBaseProperty,SomeOtherProperty1,SomeOtherProperty2" },
{ "NestedClass", "SomeNestedProp1, SomeNestedProp2"},
{ "COMPLAINT", "BaseId,SomeProperty,SomeProperty1,SomeNestedProp1" },
};
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
IEnumerable<(ModelType[] attributes, string propWithAttribute)> tt = (from x in base.GetSerializableMembers(type)
where x.GetCustomAttributes(typeof(ModelType), false).Length > 0
let attributes = (ModelType[])x.GetCustomAttributes(typeof(ModelType), false)
let propWithAttribute = x.Name
select (attributes, propWithAttribute));
var moduleType = (ModelType[])type.GetCustomAttributes(typeof(ModelType), false);
List<string> requiredProperties = (from key in configDerivedList
where moduleType.All(mod => mod
.TargetModuleName
.ToString() == key.Key) &&
tt.All(a => a
.attributes
.All(mod => mod
.TargetModuleName
.ToString() == key.Key))
select key).FirstOrDefault().Value.Split(',').ToList();
requiredProperties.AddRange(from propss in properties
from t in tt
where propss.PropertyName == t.propWithAttribute
select propss.PropertyName);
properties = properties.Where(prop => requiredProperties.Contains(prop.PropertyName, new StringComparer())).ToList();
return properties;
}
}
In the sample converter above, I would also like to serialize properties based on the attribute values, i.e. selective properties based on the attributes set on the classes.
Hope I could elaborate the use case.
Thanks in advance.

Prepare json on C# for response to API call and deserialize it on typescript (Angular 6) [duplicate]

In a WebAPI project, i have a controller that checks a status of a product, based on a value the user enters.
Lets say they enter "123" and the response should be "status": 1, AND a list of products. If they enter "321" the "status" is 0, AND a list of products.
My question is, how do i build such a string correct in a WebAPI controller.
[Route("{value:int}")]
public string GetProducts(int value)
{
var json = "";
var products = db.Products;
if (products.Any())
{
foreach (var s in products)
{
ProductApi product = new ProductApi();
product.Name = s.Name;
json += JsonConvert.SerializeObject(supplier);
}
}
var status = db.Status;
if (status.Any())
{
json += "{status:1}";
}
else
{
json += "{status:0}";
}
return json;
}
public class ProductApi
{
public string Name { get; set; }
}
Also, is this output/response considered valid?
[
{
"id":1,
"name":"product name"
},
{
"id":2,
"name":"product name 2"
},
{
"id":3,
"name":"product name 3"
}
]
{
"status": 0
}
So here are the changes for your post:
First, you should make your api return Json by default when you pass a text/html request (is this you are looking for?), adding this line to your WebApiConfig class:
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
Second, I changed the code to return a real object, impersonating your response:
public class ProductApiCollection
{
public ProductApi[] Products { get; set; }
public byte Status { get; set; }
}
public class ProductApi
{
public string Name { get; set; }
}
Method body:
public ProductApiCollection Get()
{
var result = new ProductApiCollection();
var dbProducts = db.Products;
var apiModels = dbProducts.Select(x => new ProductApi { Name = x.Name } ).ToArray();
result.Products = apiModels;
var status = db.Status.Any() ? 1 : 0;
result.Status = status;
return result;
}
This will results in the following example json:
{
"Products": [
{
"Name": "Pork"
},
{
"Name": "Beef"
},
{
"Name": "Chicken"
},
{
"Name": "Salad"
}
],
"Status": 1
}
I strongly advise you not to do manual formatting for such things, and rely on built-in and 3rd party libraries. Otherwise, you will be reinventing the things already available, tested and ready to work.
Just as raderick mentioned, you don't need to create your own custom JSON infrastructure.
public class ProductApi
{
public int Id {get;set;}
public string Name { get; set; }
}
public class ResponseDTO
{
public int Status {get;set;}
public List<ProductApi> { get; set; }
}
And in your API action, return like this:
[Route("{value:int}")]
public ResponseDTO GetProducts(int value)
{
ResponseDTO result = ...// construct response here
return result;
}

Build JSON response in Web API controller

In a WebAPI project, i have a controller that checks a status of a product, based on a value the user enters.
Lets say they enter "123" and the response should be "status": 1, AND a list of products. If they enter "321" the "status" is 0, AND a list of products.
My question is, how do i build such a string correct in a WebAPI controller.
[Route("{value:int}")]
public string GetProducts(int value)
{
var json = "";
var products = db.Products;
if (products.Any())
{
foreach (var s in products)
{
ProductApi product = new ProductApi();
product.Name = s.Name;
json += JsonConvert.SerializeObject(supplier);
}
}
var status = db.Status;
if (status.Any())
{
json += "{status:1}";
}
else
{
json += "{status:0}";
}
return json;
}
public class ProductApi
{
public string Name { get; set; }
}
Also, is this output/response considered valid?
[
{
"id":1,
"name":"product name"
},
{
"id":2,
"name":"product name 2"
},
{
"id":3,
"name":"product name 3"
}
]
{
"status": 0
}
So here are the changes for your post:
First, you should make your api return Json by default when you pass a text/html request (is this you are looking for?), adding this line to your WebApiConfig class:
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
Second, I changed the code to return a real object, impersonating your response:
public class ProductApiCollection
{
public ProductApi[] Products { get; set; }
public byte Status { get; set; }
}
public class ProductApi
{
public string Name { get; set; }
}
Method body:
public ProductApiCollection Get()
{
var result = new ProductApiCollection();
var dbProducts = db.Products;
var apiModels = dbProducts.Select(x => new ProductApi { Name = x.Name } ).ToArray();
result.Products = apiModels;
var status = db.Status.Any() ? 1 : 0;
result.Status = status;
return result;
}
This will results in the following example json:
{
"Products": [
{
"Name": "Pork"
},
{
"Name": "Beef"
},
{
"Name": "Chicken"
},
{
"Name": "Salad"
}
],
"Status": 1
}
I strongly advise you not to do manual formatting for such things, and rely on built-in and 3rd party libraries. Otherwise, you will be reinventing the things already available, tested and ready to work.
Just as raderick mentioned, you don't need to create your own custom JSON infrastructure.
public class ProductApi
{
public int Id {get;set;}
public string Name { get; set; }
}
public class ResponseDTO
{
public int Status {get;set;}
public List<ProductApi> { get; set; }
}
And in your API action, return like this:
[Route("{value:int}")]
public ResponseDTO GetProducts(int value)
{
ResponseDTO result = ...// construct response here
return result;
}

How to get the values from list of objects in c#

I have Called a Json Web Service and got the result in c#.The Json Web service data is available in format:
{
"Count": 9862,
"Items": [
{
"Admin": {
"S": "false"
},
"UserId": {
"S": "e9633477-978e-4956-ab34-cc4b8bbe4adf"
},
"Age": {
"N": "76.24807963806055"
},
"Promoted": {
"S": "true"
},
"UserName": {
"S": "e9633477"
},
"Registered": {
"S": "true"
}
},
{
"Admin": {
"S": "false"
},
"UserId": {
"S": "acf3eff7-36d6-4c3f-81dd-76f3a8071bcf"
},
"Age": {
"N": "64.79224276370684"
},
"Promoted": {
"S": "true"
},
"UserName": {
"S": "acf3eff7"
},
"Registered": {
"S": "true"
}
},
I have got the Response like this in c#:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:8000/userdetails");
try
{
WebResponse response = request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
return reader.ReadToEnd();
}
}
after finally successfully get the response i have got all the Data in string. and then parse this string in list of objects .Now I have list of objects where it showing the count in debugging.Now I want to access the values like UserId:acf3eff7-36d6-4c3f-81dd-76f3a8071bcf like properties.I dont know how to do it.Please help me and any help will be appreciated.
You can use the following code to get the values from json as:
JObject obj = JObject.Parse(json);
int count = (int)obj["Count"];
var Items = obj["Items"];
foreach (var item in Items)
var admin = item["Admin"];
Quick and dirty way:
//deserialize your string json using json.net
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
//get value of UserId in first Item
var UserId = jsonObj["Items"][0]["UserId"]["S"];
//OR get the value of UserId for each Item in Items
//foreach(dynamic item in jsonObj["Items"])
//item["UserId"]["S"];
Advice is to use c# objects as mentioned by #yousuf
To be able to access Json property like common C# object property, you need to deserialize json string to strongly typed object (you can use, for example, JSON.NET to do deserialization).
Another handy tool is http://json2csharp.com/. Paste your Json there then you can generate classes definitions that suitable to map the Json automatically :
//RootObject class definition generated using json2csharp.com
//the rest of class definition removed for brevity.
public class RootObject
{
public int Count { get; set; }
public List<Item> Items { get; set; }
}
........
........
//in main method
var jsonString = .....;
//deserialize json to strongly-typed object
RootObject result = JsonConvert.DeserializeObject<RootObject>(jsonString);
foreach(var item in result.Items)
{
//then you can access Json property like common object property
Console.WriteLine(item.UserId.S);
}
you are deserializing string to c# object. you will need to create object that reperesents the json .
For example -
public class Admin
{
public string S { get; set; }
}
public class UserId
{
public string S { get; set; }
}
public class Age
{
public string N { get; set; }
}
public class Promoted
{
public string S { get; set; }
}
public class UserName
{
public string S { get; set; }
}
public class Registered
{
public string S { get; set; }
}
public class RootObject
{
public Admin Admin { get; set; }
public UserId UserId { get; set; }
public Age Age { get; set; }
public Promoted Promoted { get; set; }
public UserName UserName { get; set; }
public Registered Registered { get; set; }
}
Then deserialize json string to object using jsonSerializer
JavaScriptSerializer serializer = new JavaScriptSerializer();
var result =
(RootObject)serializer .DeserializeObject("Json String")
string json = #"{
""Name"": ""Apple"",
""Expiry"": new Date(1230422400000),
""Price"": 3.99,
""Sizes"": [
""Small"",
""Medium"",
""Large""
]
}";
JObject o = JObject.Parse(json);
//This will be "Apple"
string name = (string)o["Name"];

Categories

Resources