0
votes

I'm loading categories from an ASP.NET Core backend as an array into a Xamarin app for a ListView with grouped items like here https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/customizing-list-appearance#grouping.

09-06 23:25:36.679 W/SSLCertificateSocketFactory(29717): Bypassing SSL security checks at caller's request
[0:] Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'BoerPlaza.Models.Products.Category' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.
Path '[0].id', line 1, position 7.
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x003a0] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0006d] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList (System.Collections.IList list, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, System.String id) [0x00173] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) [0x000dc] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x0007f] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x000db] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00054] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.JsonSerializer.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x0002d] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <2073514815234917a5e8f91b0b239405>:0 
  at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value) [0x00000] in <2073514815234917a5e8f91b0b239405>:0 
  at BoerPlaza.Services.CategoryDataStore.GetCategoriesParentAsync () [0x0013b] in C:\Users\QuanDar\source\repos\BoerPlaza\Frontend\BoerPlaza\BoerPlaza\Services\CategoryDataStore.cs:47 
  at BoerPlaza.ViewModels.BrowseCategoryViewModel.ExecuteLoadCategoriesCommand () [0x00068] in C:\Users\QuanDar\source\repos\BoerPlaza\Frontend\BoerPlaza\BoerPlaza\ViewModels\BrowseCategoryViewModel.cs:49 

The problem with the model is that you have to inherit from ObservableCollection. All the other models are working fine, it's just this one I can't make work because of the inheritance. When I remove the inheritance, everything works great, but I can't group my items in the ListView then.

Tried List instead of ObservableCollection. Does not matter. Xamarin Model: (EF Core model not included, but JSON should show what is available)

    public class Category : ObservableCollection<Category>
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public Category Parent { get; set; }
        public Image Image { get; set; }
        public ObservableCollection<Category> Children => this;

        public Category(string title)
        {
            this.Title = title;
        }

        public Category()
        {

        }
     }

JSON from backend:

  [
      {
      "id": 1,
      "title": "Vlees",
      "description": null,
      "parent": null,
      "image": {
        "alt": "Bedrijven WordPress themes",
        "pathThumbnail": "Templates/constructie.jpg"
      },
      "children": [
        {
        "id": 5,
        "title": " Kip",
         "description": null,
         "image": {
           "alt": "Bedrijven WordPress themes",
           "pathThumbnail": "Templates/constructie.jpg"
          },
          "children": []
          },
          {
         "id": 6,
         "title": "Koe",
         "description": null,
         "image": {
           "alt": "Bedrijven WordPress themes",
           "pathThumbnail": "Templates/constructie.jpg"
         },
        "children": []
       }
      ]
      },
      {
      "id": 2,
    "title": "Groente",
    "description": null,
    "parent": null,
    "image": {
      "alt": "Bedrijven WordPress themes",
      "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": [
      {
      "id": 9,
      "title": "Boemkool",
      "description": null,
      "image": {
      "alt": "Bedrijven WordPress themes",
      "pathThumbnail": "Templates/constructie.jpg"
      },
      "children": []
    }
    ]
    },
    {
    "id": 3,
    "title": "Fruit",
    "description": null,
    "parent": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": [
    {
    "id": 10,
    "title": "Appel",
    "description": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": []
    },
    {
    "id": 11,
    "title": "Peer",
    "description": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": []
    }
    ]
    },
    {
    "id": 4,
    "title": "Kruiden",
    "description": null,
    "parent": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": [
    {
    "id": 12,
    "title": "Knoflook",
    "description": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": []
    },
    {
    "id": 13,
    "title": "Peterselie",
    "description": null,
    "image": {
    "alt": "Bedrijven WordPress themes",
    "pathThumbnail": "Templates/constructie.jpg"
    },
    "children": []
    }
    ]
    }
    ]

HTTP Service

public async Task<IEnumerable<Category>> GetCategoriesParentAsync()
{
    if (IsConnected)
    {
        var json = await client.GetStringAsync($"api/products/categories/parent");
        return await Task.Run(() => JsonConvert.DeserializeObject<IEnumerable<Category>>(json));
    }

    return this.categories;

}

ViewModel: When I set the array myself, everything works fine.

    public ObservableCollection<Category> Categories { get; set; }
...
    public async Task ExecuteLoadCategoriesCommand()
    {
        IsBusy = true;
    this.PreviousCategoryHolder.Add(this.Categories);

    try
    {
       
        Categories.Clear();
        // This does not work and throws the error
        //var categories  = await CategoryStore.GetCategoriesParentAsync();
        //foreach (var category in categories)
        //{
        //    Categories.Add(category);
        //}
       
        // when I do it like this, it works and displays the items.
        this.Categories = new ObservableCollection<Category> 
        {
            new Category("Vlees")
            {
                new Category("kip"),
                new Category("kip file")

            },
            new Category("Groente")
            {
                new Category("Appel"),
                new Category("Banaan")
            }
        };
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex);
    }
    finally
    {
        IsBusy = false;
    }
}

xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage  
             xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="BoerPlaza.Views.BrowsePage">
    <!-- binding context is in code behind -->
    <ContentPage.Content>
        <ListView x:Name ="categoriesListView" 
                  IsGroupingEnabled="true" 
                  ItemTapped="Handle_ItemTapped"
                  CachingStrategy="RecycleElement"
                  >
            <ListView.GroupHeaderTemplate>
                <DataTemplate>
                    <TextCell Text="{Binding Title}"/>
                </DataTemplate>
            </ListView.GroupHeaderTemplate>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell Text="{Binding Title}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </ContentPage.Content>
</ContentPage>

Thanks!

2
please check your model using json2csharp.com. To start with, none of your model properties match the naming convention used in your json - Jason
Trying to achieve this: docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/…. But then making it work with JSON. json2csharp.com just creates many models and I don't need a nested ViewList. - QuanDar
You can modify the model to eliminate the nesting if you desire, but that doesn't change the fact that the code you posted doesn't conform to the naming conventions used in your json. - Jason

2 Answers

2
votes

You could create a new viewmodel,then set the parsed data to it.

For example:

class GroupViewModel:

public class GroupViewModel:ObservableCollection<Category>
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public Category Parent { get; set; }
    //public Image Image { get; set; }
    public ObservableCollection<Category> Children => this;

    public GroupViewModel(string title)
    {
        this.Title = title;
    }
}

class Category :

public class Category 
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    //public Category parent { get; set; }
    public List<Category> Children;
 }

set the ItemSource :

var data = JsonConvert.DeserializeObject<List<Category>>(json);// the json you request from service
ObservableCollection<GroupViewModel> groupList = new ObservableCollection<GroupViewModel>();
foreach (var item in data)
{
       GroupViewModel children = new GroupViewModel(item.Title);
       foreach (var i in item.Children)
       {
           children.Add(i);
       }
       groupList.Add(children);
 }
 categoriesListView.ItemsSource = groupList;
-1
votes

Making use of the NewtonSoft nuget (https://www.nuget.org/packages/Newtonsoft.Json/) you can define a bijection between the json properties and the model properties. Just like this:

 public class Product
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("price")]
    public decimal Price { get; set; }

    [JsonProperty("imageUrl")]
    public string ImageUrl { get; set; }
}

Then your HTTP service must look like this:

var response = await client.GetAsync(your_url);
var result = await response.Content.ReadAsStringAsync();
var list = JsonConvert.DeserializeObject<List<Product>>(result);

Your backend endpoint should be looking like this:

public async Task<IActionResult> GetProductsAsync()
    {
        var dataContext = productRepository.GetAll();
        return Ok(await dataContext.ToListAsync());
    }

I am using a repository pattern design, tho you can just handle the DataContext DB directly