Toon posts:

[C# / JSON] Deserialize nested Objects

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Doel: Maken van een Android app, geschreven in Visual Studio 2017 met Xamarin en C#.
Waar ligt het probleem: Het opslaan van een property "id" uit een nested object "project" in een JSON stream.

Ik heb twee JSON streams die ik in een SQLite database wil zetten. Deze streams krijg ik terug van een API en kan ik kwa structuur niet aanpassen. Het gaat hier om een Model Project en een Model Task, waarbij een Project object meerdere Task objecten kan hebben.

De JSON met een array van Project objecten:
code:
1
2
3
4
5
6
7
8
9
10
[
    {
        "id": 3,
        "description": "Project A"
    },
    {
        "id": 6,
        "description": "Project B"
    }
]


De JSON met een array van Task objecten:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[
    {
        "id": 27,
        "project": {
            "id": 3,
            "description": "Project A"
        },
        "description": "Task 1"
    },
    {
        "id": 30,
        "project": {
            "id": 3,
            "description": "Project A"
        },
        "description": "Task 2"
    },
    {
        "id": 33,
        "project": {
            "id": 6,
            "description": "Project B"
        },
        "description": "Task 3"
    },
    {
        "id": 36,
        "project": {
            "id": 6,
            "description": "Project B"
        },
        "description": "Task 4"
    }
]



De models / classes zien er als volgt uit:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using Newtonsoft.Json;
using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes;

namespace BettyMultiModel.Resources.Model
{
    public class Models
    {

    }

    class Project : Models
    {


        [JsonProperty("id"), PrimaryKey]
        public int Id { get; set; }


        [JsonProperty("description")]
        public string Description { get; set; }

    }

    class Task : Models
    {

        [PrimaryKey]
        public int Id { get; set; }

        public string Description { get; set; }

        [JsonProperty("ProjectId"), ForeignKey(typeof(Project))]   // <----- Dit gaat niet goed!!!
        public int ProjectId { get; set; }

        [ManyToOne]
        public Project Project { get; set; }

    }

}



Het Deserializen en Inserten in de database gaat via de volgende Method:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using Newtonsoft.Json;
.
.
.
// Insert Records of type TType into database.
private async Task<string> InsertRecords<TType>(HttpResponseMessage response)
        where TType : Models
{
    try
    {
        DataBase db = new DataBase();

        string responseString = await response.Content.ReadAsStringAsync();
        var responseBody = JsonConvert.DeserializeObject<List<TType>>(responseString);


        foreach (TType item in responseBody)
        {
            bool resultInsert = db.Insert(item);
        }

        return ResponseToMessage(response, "Insert Records");
    }
    catch (Exception ex)
    {
        Log.Info("DeserializeObject", ex.Message);
        throw;
    }            

}


Nu gaat eigenlijk alles goed, behalve het opslaan van het ProjectId in het Task object. Via Methods, die hier niet staan weergegeven, worden netjes de database en de tabellen aangemaakt. Met tabel Project, de properties 'Id' en 'Description' en tabel Task, de properties 'Id', 'Description' en ProjectId. Alle Objects uit de JSON worden netjes in de database gezet, alleen blijft de waarde van ProjectId en de records van Task op 0

Project model in de database:
Afbeeldingslocatie: https://image.ibb.co/ivWpkQ/Screenshot_2017_07_19_22_25_12.png

Task model in de database:
Afbeeldingslocatie: https://preview.ibb.co/eGcqs5/Screenshot_2017_07_19_22_25_02.png

Ik heb verschillende varianten van [JsonProperty("ProjectId")] geprobeerd, met Project.Id, project.id e.d. Veel gezocht naar C# JSON Deserialize nested Objects. Er komen dan veel voorbeelden naar voren, maar dit blijken geen Objecten in Objecten te zijn, maar Arrays in Objecten. Ook heb ik de documentatie van Newtonsoft.Json bekeken maar ik word hier niet echt wijzer van.

[ Voor 6% gewijzigd door Verwijderd op 20-07-2017 12:54 . Reden: Verduidelijking van informatie ]

Alle reacties


Acties:
  • 0 Henk 'm!

  • mulder
  • Registratie: Augustus 2001
  • Laatst online: 11:11

mulder

ik spuug op het trottoir

Het deserializen en in het opslaan van de objecten zijn twee verschillende zaken. Wat je eerst moet controleren is of het deserializen goed gaat, dwz bevat responseBody al wel de Tasks? Ik denk het want ik zie nergens een property op Project met een collectie/list/ARRAY van Tasks.

oogjes open, snaveltjes dicht


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
mulder schreef op woensdag 19 juli 2017 @ 21:32:
Het deserializen en in het opslaan van de objecten zijn twee verschillende zaken. Wat je eerst moet controleren is of het deserializen goed gaat, dwz bevat responseBody al wel de Tasks? Ik denk het want ik zie nergens een property op Project met een collectie/list/ARRAY van Tasks.
Ik heb nog even 2 screenshots van de database onderaan de originele post toegevoegd. Deze laten zien wat er wel en niet opgeslagen wordt.

De responseBody bevat dus inderdaad de Task objecten. Ik heb de Task objecten ook nog even bekeken voordat ze naar de Insert Method worden gestuurd. Hierbij heeft de property ProjectId al de waarde 0 en niet de waarde die de JSON objecten meegeven.

Ik ga er even vanuit dat je in je laatste zin "Ik denk het niet..." bedoelde.

Ik heb een List van Task aan Project toegevoegd, dit maakt echter geen verschil in de uitkomst.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    class Project : Models
    {

        [JsonProperty("id"), PrimaryKey]
        public int Id { get; set; }


        [JsonProperty("description")]
        public string Description { get; set; }

        [OneToMany]
        public List<Task> Task { get; set; }

    }

[ Voor 24% gewijzigd door Verwijderd op 19-07-2017 22:48 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Kun je niet simpel zoiets doen? (beetje roestig in C#)
C#:
1
2
3
4
5
        foreach (TType item in responseBody)
        {
            item.projectid = item.project.id;
            bool resultInsert = db.Insert(item);
        }

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Verwijderd schreef op woensdag 19 juli 2017 @ 22:54:
Kun je niet simpel zoiets doen? (beetje roestig in C#)
C#:
1
2
3
4
5
        foreach (TType item in responseBody)
        {
            item.projectid = item.project.id;
            bool resultInsert = db.Insert(item);
        }
Dit is inderdaad een goede optie. Al vraag ik me af of het niet een "dirty" oplossing is. De method is een Generic Method die je nu voor dat type object gaat customizen. Volgens mij, voor het mooie, zou dit juist op je Class allemaal bij elkaar moeten staan. Dit is echter mijn mening als C# noob O-) , ik weet niet hoe hier door de profs over gedacht wordt.

Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Verwijderd schreef op woensdag 19 juli 2017 @ 23:04:
[...]

Dit is inderdaad een goede optie. Al vraag ik me af of het niet een "dirty" oplossing is. De method is een Generic Method die je nu voor dat type object gaat customizen. Volgens mij, voor het mooie, zou dit juist op je Class allemaal bij elkaar moeten staan. Dit is echter mijn mening als C# noob O-) , ik weet niet hoe hier door de profs over gedacht wordt.
Het is inderdaad een niet echt fraaie oplossing.

Je kunt het op een beter gestructureerde manier oplossen door een eigen JsonConverter te implementeren voor de Task POCO. Die converter kan voor elke individuele taak de geneste project.id property uit de JSON pakken en deze toekennen aan een directe ProjectId eigenschap op de Task C# POCO.

De implementatie van de ReadJson method van de converter zou dan zoiets worden:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public override object ReadJson(
  JsonReader reader,
  Type objectType,
  object existingValue,
  JsonSerializer serializer
) {
  JObject obj = JObject.Load(reader);
  JToken token = obj.SelectToken("project.id");

  if (token == null)
  {
    // No property to transplant; just pass the original reader on to the base implementation.
    return base.ReadJson(reader, objectType, existingValue, serializer);
  }

  // Transplant the property onto our 'root' JObject and pass on a patched reader to the
  // base implementation.
  obj.Add("projectId", token.DeepClone());
  using (JsonReader patchedReader = obj.CreateReader())
  {
    return base.ReadJson(patchedReader, objectType, existingValue, serializer);
  }
}

[ Voor 12% gewijzigd door R4gnax op 20-07-2017 00:23 ]


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Je gebruikt zo te zien WebApi (iig System.Net), dus waarom laat je het deserialiseren niet gewoon aan WebApi over?

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

Verwijderd

Verwijderd schreef op woensdag 19 juli 2017 @ 23:04:
De method is een Generic Method die je nu voor dat type object gaat customizen.
Generic? Hoezo? Je gebruikt toch ook dit? ;)

C#:
1
2
        [JsonProperty("ProjectId"), ForeignKey(typeof(Project))]   // <----- Dit gaat niet goed!!!
        public int ProjectId { get; set; }

Acties:
  • 0 Henk 'm!

  • Teknix1982
  • Registratie: Januari 2005
  • Niet online
Heb je de setter van ProjectId echt nodig? Hij komt toch niet uit jouw Json. Anders kun je dit proberen in de Task model.
C#:
1
public int ProjectId { get { return project.Id; } }

[ Voor 15% gewijzigd door Teknix1982 op 20-07-2017 07:05 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Grijze Vos schreef op donderdag 20 juli 2017 @ 00:18:
Je gebruikt zo te zien WebApi (iig System.Net), dus waarom laat je het deserialiseren niet gewoon aan WebApi over?
Ik gebruik JsonConvert.DeserializeObject van Newtonsoft.Json. Deze wordt in de meeste (Xamarin) forums en tutorials aangeraden om te gebruiken. Het doel is een Android app te maken met C# en Xamarin.

R4gnax schreef op donderdag 20 juli 2017 @ 00:17:
[...]


Het is inderdaad een niet echt fraaie oplossing.

Je kunt het op een beter gestructureerde manier oplossen door een eigen JsonConverter te implementeren voor de Task POCO. Die converter kan voor elke individuele taak de geneste project.id property uit de JSON pakken en deze toekennen aan een directe ProjectId eigenschap op de Task C# POCO.

De implementatie van de ReadJson method van de converter zou dan zoiets worden:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public override object ReadJson(
  JsonReader reader,
  Type objectType,
  object existingValue,
  JsonSerializer serializer
) {
  JObject obj = JObject.Load(reader);
  JToken token = obj.SelectToken("project.id");

  if (token == null)
  {
    // No property to transplant; just pass the original reader on to the base implementation.
    return base.ReadJson(reader, objectType, existingValue, serializer);
  }

  // Transplant the property onto our 'root' JObject and pass on a patched reader to the
  // base implementation.
  obj.Add("projectId", token.DeepClone());
  using (JsonReader patchedReader = obj.CreateReader())
  {
    return base.ReadJson(patchedReader, objectType, existingValue, serializer);
  }
}
Bedankt. Dit zal ik even gaan bestuderen.

Teknix1982 schreef op donderdag 20 juli 2017 @ 07:02:
Heb je de setter van ProjectId echt nodig? Hij komt toch niet uit jouw Json. Anders kun je dit proberen in de Task model.
C#:
1
public int ProjectId { get { return project.Id; } }
Hij staat toch hier in de JSON van de Task?

code:
1
2
3
4
5
6
7
8
9
10
11
[
    {
        "id": 27,
        "project": {
            "id": 3, // <------------------------------------------ Project ID
            "description": "Project A"
        },
        "description": "Task 1"
    },
....
]

[ Voor 59% gewijzigd door Verwijderd op 20-07-2017 10:49 ]


Acties:
  • 0 Henk 'm!

  • Knutselsmurf
  • Registratie: December 2000
  • Laatst online: 10:53

Knutselsmurf

LED's make things better

Op welke wijze wordt de relatie tussen het veld 'ProjectId' en de property 'Project' geregeld binnen de Task-class? Ik verwacht niet dat dit automatisch gebeurt aan de hand van het type 'ForeignKey(typeof(Project))'

- This line is intentionally left blank -


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Knutselsmurf schreef op donderdag 20 juli 2017 @ 10:59:
Op welke wijze wordt de relatie tussen het veld 'ProjectId' en de property 'Project' geregeld binnen de Task-class? Ik verwacht niet dat dit automatisch gebeurt aan de hand van het type 'ForeignKey(typeof(Project))'
Dit weet ik niet. Ik probeer dit te leren en ben daarom begonnen met een hele simpele app om het concept te leren begrijpen. Ik heb hiervoor een webapplicatie (APAAS) waar ik 2 models in heb gemaakt; Project en Task. Voor het gemak heb ik beide models maar 2 properties gegeven; id en description. Tussen de 2 models heb ik een "has many"-relatie aangemaakt, zo gaat dat op dit platform; een project kan meerdere taken hebben. Vervolgens "roep" ik vanuit mijn app om de gegevens uit de Project en Task models. Dit krijg ik terug in JSON. De structuur van de JSON, die ik ontvang vanuit de webapplicatie, kan ik niet aanpassen.

De classes begonnen zo:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace BettyMultiModel.Resources.Model
{
    public class Models
    {

    }

    class Project : Models
    {
        public int Id { get; set; }

        public string Description { get; set; }
    }

    class Task : Models
    {
        public int Id { get; set; }

        public string Description { get; set; }
    }

}


Hiermee kan ik prima de basis/root gegevens van elk object deserializen en opslaan in de database. Het enige is dat ik er 1 property bij wil in de Task Class, namelijk ProjectId, zodat ik deze ook kan opslaan in de database op het record van elke Task.

De opmaak van de classes is in het zoeken naar een oplossing wat veranderd door dingen die ik zelf heb geprobeerd en dingen die mij zijn voorgesteld om te doen in de "hoop" dat dit het probleem zou oplossen.

Acties:
  • 0 Henk 'm!

  • Knutselsmurf
  • Registratie: December 2000
  • Laatst online: 10:53

Knutselsmurf

LED's make things better

De attribute ForeignKey(typeof(Project)) werkt alleen bij het uit de database hale van je objecten.

Wat gebeurt er bij het binnen halen van de JSON?

1) Je haalt een lijst met Projecten op in een lokale variable.
2) Je haalt een lijst met Tasks op in een lokale variable. In iedere Task is alleen het veld ProjectID gevuld.

Hoet weet je code nu dat er uit de (lokale) lijst met Projects het juiste Project gekozen moet worden om in de Task te stoppen? Vanuit de JSON- kant bekeken ligt er geen enkele relatie tussen de lijst met Projecten en de lijst met Tasks. Je zult dus zelf bij het deserializen van een Task-object in de lijst met Projecten op zoek moeten naar het juiste project.

De koppeling tussen een Project-object en een Task-object is wel in de database aanwezig, maar de datastroom is de andere kant op. En daar is deze koppeling niet automatisch aanwezig.

- This line is intentionally left blank -


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Dank je wel voor je uitleg.
Knutselsmurf schreef op donderdag 20 juli 2017 @ 11:35:
De attribute ForeignKey(typeof(Project)) werkt alleen bij het uit de database hale van je objecten.

Wat gebeurt er bij het binnen halen van de JSON?

1) Je haalt een lijst met Projecten op in een lokale variable.
2) Je haalt een lijst met Tasks op in een lokale variable. In iedere Task is alleen het veld ProjectID gevuld.
Dit klopt niet helemaal: In iedere (locale) Task is alleen het veld ProjectID niet gevuld.
Hoet weet je code nu dat er uit de (lokale) lijst met Projects het juiste Project gekozen moet worden om in de Task te stoppen? Vanuit de JSON- kant bekeken ligt er geen enkele relatie tussen de lijst met Projecten en de lijst met Tasks. Je zult dus zelf bij het deserializen van een Task-object in de lijst met Projecten op zoek moeten naar het juiste project.
Ik begrijp inderdaad dat er een link gelegd moet worden om bij het tonen van een Project een lijst met de bijbehorende Tasks te kunnen laten zien. Ik dacht dat het opslaan van het "id" uit het nested "project" in een Task object van de JSON hiervoor voldoende zou zijn.

Zoals je hier hebt:
Afbeeldingslocatie: https://viralpatel.net/blogs/wp-content/uploads/2011/12/one-to-many-relationship-diagram.png

Vervolgens met een query alle Tasks ophalen waarvan het ProjectId gelijk is aan het Id van het project waar ik op dat moment "naar kijk".
De koppeling tussen een Project-object en een Task-object is wel in de database aanwezig, maar de datastroom is de andere kant op. En daar is deze koppeling niet automatisch aanwezig.
Elke suggestie ter verbetering van mijn code/kennis is dus welkom. _/-\o_

Acties:
  • 0 Henk 'm!

  • Teknix1982
  • Registratie: Januari 2005
  • Niet online
Verwijderd schreef op donderdag 20 juli 2017 @ 10:17:

[...]


Hij staat toch hier in de JSON van de Task?

code:
1
2
3
4
5
6
7
8
9
10
11
[
    {
        "id": 27,
        "project": {
            "id": 3, // <------------------------------------------ Project ID
            "description": "Project A"
        },
        "description": "Task 1"
    },
....
]
Nee, dat wordt het id veld in de Project model in de Task model. Het veld ProjectId staat niet in jouw Json. Daarom kun je die zo implementeren als ik suggereerde.

Acties:
  • 0 Henk 'm!

  • Oxidda
  • Registratie: Maart 2010
  • Laatst online: 25-12-2023

Oxidda

Heer Opblaaskrokodil

Gebruik je entity framework bij het opslaan van data? If not, then what? SQL Lite orm gebeuren zo te zien.

Zit de logica voor opslaan naar de database in de App of op een server?

[ Voor 12% gewijzigd door Oxidda op 20-07-2017 12:37 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Oxidda schreef op donderdag 20 juli 2017 @ 12:30:
Gebruik je entity framework bij het opslaan van data? If not, then what? SQL Lite orm gebeuren zo te zien.

Zit de logica voor opslaan naar de database in de App of op een server?
Nee, dit gebruik ik niet. Ik ga me hier even in verdiepen.

De logica voor het opslaan voor het opslaan van de de gegevens in de App, zit in de App. Hetis een Android App met een SQLite database.

Acties:
  • 0 Henk 'm!

  • Oxidda
  • Registratie: Maart 2010
  • Laatst online: 25-12-2023

Oxidda

Heer Opblaaskrokodil

Verwijderd schreef op donderdag 20 juli 2017 @ 12:41:
[...]


Nee, dit gebruik ik niet. Ik ga me hier even in verdiepen.

De logica voor het opslaan voor het opslaan van de de gegevens in de App, zit in de App. Hetis een Android App met een SQLite database.
Het feit dat je die attributen gebruikt , dat bedoel ik ermee te zeggen. Als het EF iets was dan had ik het wel geweten vandaar dat ik het vroeg, maar ik had gewoon niet goed gekeken naar de code.

Ik snap dan nog even niet waarom je vanuit JSON werkt en niet gewoon meteen met objecten bezig bent, komt de JSON weer ergens vandaan en die importeer je?

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Oxidda schreef op donderdag 20 juli 2017 @ 12:44:
[...]


Het feit dat je die attributen gebruikt , dat bedoel ik ermee te zeggen. Als het EF iets was dan had ik het wel geweten vandaar dat ik het vroeg, maar ik had gewoon niet goed gekeken naar de code.
Geen probleem.
Ik snap dan nog even niet waarom je vanuit JSON werkt en niet gewoon meteen met objecten bezig bent, komt de JSON weer ergens vandaan en die importeer je?
Ja, de JSON komt van een externe bron, die ik lokaal in een database wil hebben. Dit staat overigens ook in mijn eerste post. Ik heb juist mijn best gedaan om zo veel mogelijk info te verschaffen. O-)

Acties:
  • +1 Henk 'm!

  • ThoNohT
  • Registratie: September 2006
  • Laatst online: 07-10 14:51
Het grootste probleem lijkt me dat de datastructuren in de json anders zijn dan in de database, maar toch probeer je ze te hergebruiken, dit gaat vervolgens fout op de ene property waarin dit verschil van belang is.

Het netste zou mijns inziens zijn om deze klassen te splitsen. Je kunt deze objecten zien als data transfer objects (DTO), en (database) entities. Het verschil hierbij is dat de dto een project property heeft waarvan jij alleen de identifier interessant vindt, terwijl de entity alleen de de project identifier property heeft.

Nu kun je (via extension methods, een helper class, extra functies op de DTOs, whatever) conversie definiëren van DTO -> entity, voor zowel project als task, en deze entity aan de database toevoegen.

Dezelfde oplossing (handmatig invullen ProjectId) is eerder al genoemd, maar zag er toen wat lelijk uit. Op deze manier is het juist logisch om het te doen, en hou je in je code ook nog eens een duidelijke scheiding tussen de twee verschillende soorten objecten.

Acties:
  • +1 Henk 'm!

Verwijderd

Topicstarter
Bedankt allen voor de reacties! _/-\o_

De opzet in mijn eerste post is een klein onderdeel van wat ik uiteindelijk wil gaan maken. Uiteindelijk zal ik een stuk of 10/15 tabellen moeten gaan ophalen van de server, het is dan niet de bedoeling dat ik voor elke model allerlei importfuncties moet gaan schrijven, wat volgens mij niet nodig is als ik de juiste techniek en theorie gebruik. Ik merk dat mijn kennis is op dit moment alleen nog te beperkt om goed te beoordelen wat ik hier het beste, al dan niet uit jullie antwoorden, kan toepassen.

Onder het motto "Zonder een stevige fundering staat je huis niet stevig." neem ik even een stapje terug om meer basiskennis op te doen... O-)
Pagina: 1