Serialize/deserialize IPagedList<T> using JSON.NET - c#

I have the following use case, I want to serialize/deserialize my IPagedList of T using JSON.net (Newtonsoft.Json). This seems not to be working. It does not serialize everything, only the items (or with my own ContractResolver, the object with empty properties). I am using the X.PagedList nuget.
A test case:
var list = new List<int>(Enumerable.Range(1, 1000));
var paged = list.AsQueryable().ToPagedList(3, 10);
var json = JsonConvert.SerializeObject(paged); (output: [21,22,23,24,25,26,27,28,29,30])
var obj = JsonConvert.DeserializeObject<PagedList<int>>(json);
When I extend the DefaultContractResolver and override the CreateContract method, it deserializes to the IPagedList object but with no properties filled, and also no items.. I tried to override the CreateProperties but that didn't work as I expected.
protected override JsonContract CreateContract(Type objectType)
{
if (typeof(IPagedList).IsAssignableFrom(objectType))
{
return CreateObjectContract(objectType);
}
return base.CreateContract(objectType);
}
So how can I successfully serialize/deserialize this class?

When you serialize IPagedList it serializes the contents/data of the requested page and not the meta data (i.e. FirstItemOnPage, HasNextPage, etc. properties) will be lost.
So to overcome this issue you can create an anonymous class like the following which will have two properties
items holds current page contents
metaData holds super list metadata.
So change first part of your code to:
var list = new List<int>(Enumerable.Range(1, 1000));
var paged = list.AsQueryable().ToPagedList(3, 10);
var pagedWithMetaData = new { items = paged, metaData = paged.GetMetaData() };
then serialize that object
//Serialize
var json = JsonConvert.SerializeObject(pagedWithMetaData);
this will generate a sub-list with the metaData for your example it is like following:
{"items":[21,22,23,24,25,26,27,28,29,30],"metaData":{"PageCount":100,"TotalItemCount":1000,"PageNumber":3,"PageSize":10,"HasPreviousPage":true,"HasNextPage":true,"IsFirstPage":false,"IsLastPage":false,"FirstItemOnPage":21,"LastItemOnPage":30}}
now to deserialize you need to create three classes:
PaginationMetaData class to encapsulate the meta-data so when we deserialize the sub list it's metadata will be deserialzed to this type.
PaginationEntityClass where T : class class to encapsulate the anonymous class so when we deserialize the sub list its content and metadata will be deserialzed to this type.
PaginationEntityStruct where T : struct same as PaginationEntityClass but T here is a struct so it hold struct types instead of classes like your case for int.
So these three classes will as follow:
public class PaginationMetaData
{
public int FirstItemOnPage { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
public bool IsFirstPage { get; set; }
public bool IsLastPage { get; set; }
public int LastItemOnPage { get; set; }
public int PageCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalItemCount { get; set; }
}
public class PaginationEntityClass<T> where T : class
{
public PaginationEntityClass()
{
Items = new List<T>();
}
public IEnumerable<T> Items { get; set; }
public PaginationMetaData MetaData { get; set; }
}
public class PaginationEntityStruct<T> where T : struct
{
public PaginationEntityStruct()
{
Items = new List<T>();
}
public IEnumerable<T> Items { get; set; }
public PaginationMetaData MetaData { get; set; }
}
Now to deserialize you need just to deserialize to PaginationEntityStruct of integers then use the second constructor overload of StaticPagedList class (class comes with PagedList package) to recreate the subclass with metadata.
//Deserialize
var result = JsonConvert.DeserializeObject<PaginationEntityStruct<int>>(json);
StaticPagedList<int> lst = new StaticPagedList<int>(
result.Items,
result.MetaData.PageNumber,
result.MetaData.PageSize,
result.MetaData.TotalItemCount);

Perhaps you could consider using the built in LINQ functions .skip() & .take().
As per my comment above it seems the repo for PagedList hasn't been maintained in a long while. Before knocking your head against the wall perhaps try the above built in functions.
PagedList github repo.
Samples for Skip & Take

Related

How to fix infinite object nesting or set a max depth level with AutoMapper?

I am working on a project where I need to map a list of a specific object to another list. You can find the objects below. I need to serialize the list into a JSON string, but it throws the next error:
System.Text.Json.JsonException: 'A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.'
The objects (cutted):
class Class1
{
public string Name { get; set; }
public List<Product> Products { get; set; } = new List<Product>();
}
class Product
{
public string Name { get; set; }
public Class1 Class1 { get; set; }
}
class Class1Model
{
public string Name { get; set; }
public IEnumerable<Product> Products { get; set; }
}
class ProductModel
{
public string Name { get; set; }
public Class1Model Class1 { get; set; }
}
This is my mapperprofile:
public class MyMapper : Profile
{
public MyMapper()
{
CreateMap<Class1, Class1Model>().MaxDepth(2);
CreateMap<Product, ProductModel>().MaxDepth(2);
}
}
When debugging, my mapped list shows infinite nested objects. For example:
Class1Model
--> Products, each Product
--> Class1Model
--> Products, each Product
--> Class1Model
--> repeating....
The MaxDepth configuration in my MapperProfile doesn't fix this issue. I want a real max depth of for example 2, so that my System.Text.Json.JsonSerializer doesn't break. We deliberately don't want to use NewtonSoft JSON.
I upgraded my System.Text.Json to v5.0.1.
I created a JsonSerializerOptions object:
var settings = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve
};
Serializing:
var json = JsonSerializer.Serialize(class1s, options: settings);
When serializing the object into a JSON string, the string is built in this way:
var deserializedJson = JsonSerializer.Deserialize<IEnumerable<Class1Model>>(json, options: settings);
{"$id":"1","$values":[{"$id":"2","Name":"t","Products":{"$id":"3","$values":[{"$id":"4","Name":"p","Class1":{"$id":"5","Name":"t","Products":{"$id":"6","$values":[{"$ref":"4"}]}}}]}}]}
Somehow, the $id, $values, and $ref tags remember how to build up the object in its original structure when deserializing as it was before serializing it into a JSON string.
This was the fix.

Convert ICollection<Interface> to ICollection<Class> [UWP] c#6.0

I know this has been answered a lot of places on stack and the best possible answer is Here. I've tried it and a lot of other answers too. I have a library that returns me a collection of an Interface (IMasterAutoSuggestOutlet)
public interface IMasterAutoSuggestOutlet : IBaseAutoSuggestOutlet
{
IAddressData AddressData { get; }
IPlaceActivity PlaceActivity { get; }
string ELoc { get; }
Geopoint ExactLocation { get; }
Geopoint EntranceLocation { get; }
LocationType TypeOfLocation { get; }
}
Now, I want to transfer this interface data from one page to another in my application. Since Interfaces cannot be serialized, I created a concrete class that implements this interface:
My Concrete Class,
public class MasterAutoSuggestModel : IMasterAutoSuggestOutlet
{
public IAddressData AddressData { get; set; }
public IPlaceActivity PlaceActivity { get; set; }
public string ELoc { get; set; }
public Geopoint ExactLocation { get; set; }
public Geopoint EntranceLocation { get; set; }
public LocationType TypeOfLocation { get; set; }
}
What I want to do is, convert the ICollection to ICollection. My code below shows my implementation of such an operation:
var collection = mainPageViewModel?.SearchPageVM?.searchManager?.AutoSuggestResponse;
var ob = collection.First();
if (ob is IMasterAutoSuggestOutlet)
{
var ToBeTransfered = collection.OfType<MasterAutoSuggestModel>(); //Simply returns the collection with a count 0
var serializedData = JsonConvert.SerializeObject(ToBeTransfered);
ScenarioFrame.Navigate(typeof(MasterSearchResultPage), serializedData);
}
The issue is with var ToBeTransfered = col.OfType<MasterAutoSuggestModel>(); it returns me a collection with count 0 even though the collection has 10 items in it.
Can someone please tell me where am I going wrong? Please note I need to use this Converted collection to serialize and send as the navigation parameter to send to the next page
The ofType method filters the connection by the type specified. If you are retrieving the objects from some other library, they would not be that specific type.
https://msdn.microsoft.com/en-us/library/bb360913(v=vs.110).aspx
What you would probably want to do is convert the items retrieved from the library into your dto for serialisation. You can use something like automapper for the conversion
if (ob is IMasterAutoSuggestOutlet) {
var transferObject = new MasterAutoSuggestModel(){
//Set Properties
}
// var ToBeTransfered = collection.OfType<MasterAutoSuggestModel>(); //Simply returns the collection with a count 0
var serializedData = JsonConvert.SerializeObject(transferObject);
ScenarioFrame.Navigate(typeof(MasterSearchResultPage), serializedData); }

DataContractJsonSerializer: collection type cannot be serialized when assigned to an interface

I am writing a WCF service that generates various XML and JSON formats for multiple clients. The code below generates a SerializationException: 'TPH_PriceListJsonItems' is a collection type and cannot be serialized when assigned to an interface type that does not implement IEnumerable ('TPH_IPriceListItems'). The XML part is working fine, but not JSON. I do not understand the error, my interface is implementing IEnumerable to represent a class wrapping a simple List<> so I can use the CollectionDataContract.
public class ReproduceDataContractIssue
{
public static void Main(String[] args)
{
// Create test object - vacation products lowest prices grid
TPH_IPriceList priceList = new TPH_PriceListJson();
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Cancun", StayDuration = 7, LowestPrice = 1111 });
priceList.ListItems.Add(new TPH_PriceListJsonItem() { DestCityName = "Jamaica", StayDuration = 14, LowestPrice = 2222 });
// Serialize into XML string
DataContractSerializer serializer = new DataContractSerializer(priceList.GetType());
MemoryStream memStream = new MemoryStream();
serializer.WriteObject(memStream, priceList);
memStream.Seek(0, SeekOrigin.Begin);
string xmlOutput;
using (var streamReader = new StreamReader(memStream))
xmlOutput = streamReader.ReadToEnd();
// Serialize into JSON string
DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(priceList.GetType());
jsonSerializer.WriteObject(memStream = new MemoryStream(), priceList);
memStream.Seek(0, SeekOrigin.Begin);
string jsonOutput;
using (var streamReader = new StreamReader(memStream))
jsonOutput = streamReader.ReadToEnd();
}
}
public interface TPH_IPriceList
{
TPH_IPriceListItems ListItems { get; set; }
}
public interface TPH_IPriceListItems : IEnumerable<TPH_IPriceListItem>, IEnumerable, IList<TPH_IPriceListItem>
{
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public TPH_IPriceListItems ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
public TPH_PriceListJsonItem()
{
}
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>, TPH_IPriceListItems, IEnumerable<TPH_IPriceListItem>, IEnumerable
{
public TPH_PriceListJsonItems(int capacity)
: base(capacity)
{
}
public TPH_PriceListJsonItems()
: base()
{
}
}
}
The inconsistency arises from a difference in how JSON and XML represent collections. For XML, the data contract serializers convert a collection to a nested set of elements -- an outer collection wrapper, and an inner element for each item in the collection. For JSON, the serializers convert a collection to an array containing objects. This seems reasonable, but there's a difference between the two: the XML outer element can have its own XML attributes, but JSON arrays cannot have their own properties -- there's simply no place for them in the standard.
This becomes an issue in dealing with type hints. Type hints are properties added to the serialized data to indicate, in the event of serializing an interface or base class of a class hierarchy, what concrete class was actually serialized. They are required to enable deserialization of the object without data loss. In XML, they appear as an i:type attribute:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V1">
<ListItems i:type="ListItems"> <!-- Notice the type hint here. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- Notice the type hint here also. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
As you can see from your own example, type hints can be added for both collection and non-collection classes.
In JSON objects, they appear as an added property named "__type":
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
}
But, as mentioned before, JSON arrays cannot have properties. So, what does DataContractJsonSerializer do for polymorphic collection types? Well, other than for a few standard collection interfaces which, as Fabian notes, are mapped to collection classes using hardcoded logic, it throws a cryptic exception to indicate that subsequent deserialization would be impossible. (For comparison, Json.NET introduces an extra container object to hold collection type information. See TypeNameHandling setting.)
The solution to this inconsistency is to serialize the collection explicitly as a concrete collection (TPH_PriceListJsonItems in your case) rather than as an interface:
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[IgnoreDataMember]
public TPH_IPriceListItems ListItems
{
get
{
return ListItemList;
}
set
{
var list = value as TPH_PriceListJsonItems;
if (list == null)
{
list = new TPH_PriceListJsonItems();
if (value != null)
list.AddRange(value);
}
ListItemList = list;
}
}
[DataMember(Name = "ListItems")]
TPH_PriceListJsonItems ListItemList { get; set; }
public TPH_PriceListJson()
{
ListItemList = new TPH_PriceListJsonItems();
}
}
This eliminates the need for the type hint on the collection element while retaining it for collection members. It generates the following XML:
<PriceList xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Question32569055.V3">
<ListItems> <!-- No type hint here any more. -->
<ListItem i:type="TPH_PriceListJsonItem"> <!-- But the type hint is still here. -->
<DestCityName>Cancun</DestCityName>
<StayDuration>7</StayDuration>
<LowestPrice>1111</LowestPrice>
</ListItem>
</ListItems>
</PriceList>
And produces the following JSON:
{
"ListItems": [
{
"__type": "TPH_PriceListJsonItem:#Question32569055.V3",
"DestCityName": "Cancun",
"StayDuration": 7,
"LowestPrice": 1111
},
]
}
This design allows the class TPH_IPriceListItems to control exactly what type of collection is used internally rather than leaving it up to users of the class, and thus seems like a preferable design overall.
Looks like it is a similar problem to this one. The List of supported interfaces in DataContractJsonSerializer is hardcoded. So you can't add your own List Wrapper interface.
Why don't you just drop TPH_IPriceListItems like in the following code ? It is simpler and should also do what you want:
public interface TPH_IPriceList
{
IList<TPH_IPriceListItem> ListItems { get; set; }
}
public interface TPH_IPriceListItem
{
string DestCityName { get; set; }
int StayDuration { get; set; }
int LowestPrice { get; set; }
}
[DataContract(Name = "PriceList")]
[KnownType(typeof(TPH_PriceListJsonItems))]
public class TPH_PriceListJson : TPH_IPriceList
{
[DataMember]
public IList<TPH_IPriceListItem> ListItems { get; set; }
public TPH_PriceListJson()
{
ListItems = new TPH_PriceListJsonItems();
}
}
[DataContract]
public class TPH_PriceListJsonItem : TPH_IPriceListItem
{
[DataMember(Order = 1)]
public string DestCityName { get; set; }
[DataMember(Order = 2)]
public int StayDuration { get; set; }
[DataMember(Order = 3)]
public int LowestPrice { get; set; }
}
[CollectionDataContract(Name = "ListItems", ItemName = "ListItem")]
[KnownType(typeof(TPH_PriceListJsonItem))]
public class TPH_PriceListJsonItems : List<TPH_IPriceListItem>
{
}

C# XML Serialization - How to Serialize attribute in Class that inherits List<Object>?

I need to create an XML file using C#.
I am using a class that inherits List that represents a list of computers and later initialize it with values but the serializer doesn't get the attributes for this class, only for its descendants.
this is the class:
public class Computers : List<Computer>
{
[XmlAttribute("StorageType")]
public int StorageType { get; set; }
[XmlAttribute("StorageName")]
public string StorageName { get; set; }
}
public class Computer
{
[XmlAttribute("StorageType")]
public int StorageType { get; set; }
[XmlAttribute("StorageName")]
public string StorageName { get; set; }
public string IPAddress { get; set; }
public string Name { get; set; }
}
The result should look something like this:
<fpc4:Computers StorageName="Computers" StorageType="1">
<fpc4:Computer StorageName="{D37291CA-D1A7-4F34-87E4-8D84F1397BEA}" StorageType="1">
<fpc4:IPAddress dt:dt="string">127.0.0.1</fpc4:IPAddress>
<fpc4:Name dt:dt="string">Computer1</fpc4:Name>
</fpc4:Computer>
<fpc4:Computer StorageName="{AFE5707C-EA71-4442-9CA8-2A6264EAA814}" StorageType="1">
<fpc4:IPAddress dt:dt="string">127.0.0.1</fpc4:IPAddress>
<fpc4:Name dt:dt="string">Computer2</fpc4:Name>
</fpc4:Computer>
But what I get so far is this:
<fpc4:Computers>
<fpc4:Computer StorageType="1" StorageName="{7297fc09-3142-4284-b2e9-d6ea2fb1be78}">
<fpc4:IPAddress>127.0.0.1</fpc4:IPAddress>
<fpc4:Name>Computer1</fpc4:Name>
</fpc4:Computer>
<fpc4:Computer StorageType="1" StorageName="{eab517f6-aca9-4d01-a58b-143f2e3211e7}">
<fpc4:IPAddress>127.0.0.1</fpc4:IPAddress>
<fpc4:Name>Computer2</fpc4:Name>
</fpc4:Computer>
</fpc4:Computers>
As you can see the Computers node which is the parent node doesn't get the attributes.
Do you guys have a solution?
XmlSerializer treats lists completely separate to leaf nodes; properties on lists do not exist - it is just a collection of the contained data. A better approach would be:
public class Computers {
private readonly List<Computer> items = new List<Computer>();
[XmlElement("Computer")]
public List<Computer> Items { get { return items; } }
[XmlAttribute("StorageType")]
public int StorageType { get; set; }
[XmlAttribute("StorageName")]
public string StorageName { get; set; }
}
This is an object that has a set of computers and has two attributes - but is not a list itself. The use of XmlElementAttribute for the list flattens the nesting as desired. Note that I have omitted namespaces for convenience.
Inheriting from a list (with an aim of adding members) will not work well, not just for XmlSerlaizer, but for a wide range of serializers and binding frameworks.

Single Method to create Sorted List<T> where T : Base Class

I have an interface for items which are Votable: (Like StackExchange, Reddit, etc...)
// Irrelevant properties left out (Creator, Upvotes, Downvotes, etc)
internal interface IVotable
{
double HotScore { get; set; }
double VoteTotal { get; set; }
DateTime CreatedDate { get; set; }
}
I have a concrete base class which extends this interface and defines a constructor to populate the default properties:
internal class SCO : IVotable
{
public double HotScore { get; set; }
public double VoteTotal { get; set; }
public DateTime CreatedDate { get; set; }
public SCO(SPListItem item, List<Vote> votes)
{
VoteTotal = UpVotes - DownVotes;
HotScore = Calculation.HotScore(Convert.ToInt32(UpVotes), Convert.ToInt32(DownVotes), Convert.ToDateTime(item["Created"]));
CreatedDate = Convert.ToDateTime(item["Created"]);
}
Here is an example of a class in use which extends this base class and it's constructor:
class Post : SCO
{
public string Summary { get; set; }
public Uri Link { get; set; }
public Post(SPListItem item, List<Vote> votes)
: base(item, votes)
{
Summary = (string) item["Summary"];
Link = new UriBuilder((string) item["Link"]).Uri;
}
}
Over 90% of the time, I'm returning sorted collections of the classes for rendering on a page.
I would like to have a generic method of some kind that takes in a collection of DataBase Items, a List of Votes to match with the Items, creates a List, and then sorts that list based on a passed ENUM that defines how to sort.
I've tried a few approaches, many of them based on previous posts. I'm not sure if I'm approaching this in the right way. While the solutions do work, I see a lot of Boxing, or Reflection, or some kind of (possibly major) sacrifice in performance for readability, or ease of use.
What is the best way to Create a Sorted List of objects that can be used in any subsequent child class as long as that class extends the base class?
Previous Approach that is working:
Create a List of <T> from a base class
A Generic List of <T> embedded in the Base Class which extends Activator.CreateInstance using reflection to return the list:
public static List<T> SortedCollection<T>(SPListItemCollection items, ListSortType sortType, List<Vote> votes) where T : SCO
Request for Sample of use:
static public List<Post> Get100MostRecentPosts(ListSortType sortType)
{
var targetList = CoreLists.SystemAccount.Posts();
var query = new SPQuery
{
Query = "<OrderBy><FieldRef Name=\"Created\" Ascending=\"False\" /></OrderBy>",
RowLimit = 100
};
var listItems = targetList.GetItems(query);
var votes = GetVotesForPosts(listItems);
return Post.SortedCollection<Post>(listItems, sortType, votes);
}

Categories

Resources