Passare tipo anonimo variabile

di il
12 risposte

Passare tipo anonimo variabile

In un programma ho un metodo che accetta due input fatto così CallService(string service, object? data = null)
da un json che mi arriva posso passare il nome del servizio e dopo devo passare l'oggetto corretto con i suoi valori e faccio così con il tipo anonimo CallService("audio", new {volume=1}) oppure CallService("messaggio", new {Titolo="Titolo", Testo="Ciao come stai?"}) oppure CallService("gps", new {LAT=40, LON=20}) ma io non so prima che servizio mi arriva dal json e i servizi sono molti e tutti con i loro oggetti diversi che possono anche essere aggiunti senza preavviso
Come faccio se ad esempio ho "{service:'audio',data:'new {volume=1}'} a trasformarlo e passarlo poi cosi CallService("audio", new {volume=1})? il json lo posso modificare acne come voglio ma mi interessa creare il tipo anonimo dalla stringa perche non so prima quali dati e quali servizi verranno usati quindi non posso scriverlo in compilazione

Grazie

12 Risposte

  • Re: Passare tipo anonimo variabile

    orecchione ha scritto:


    In un programma ho un metodo che accetta due input fatto così CallService(string service, object? data = null)
    da un json che mi arriva posso passare il nome del servizio e dopo devo passare l'oggetto corretto con i suoi valori ma io non so prima che servizio mi arriva dal json e i servizi sono molti e tutti con i loro oggetti diversi che possono anche essere aggiunti senza preavviso [...]
    Il parsing del pacchetto JSON lo farai con una libreria, suppongo, tra quelle disponibili per la gestione di questo formato.

    Usando ad esempio JSON.NET, puoi eseguire il parsing e ottenere un oggetto che "dinamicamente" dispone delle proprietà veicolate tramite questo formato: vedi questo esempio per un approfondimento.

    Se il metodo accetta un generico Object, allora anche un tipo anonimo può essere passato come parametro, come quello prodotto dalla libreria quando fa il parsing di un pacchetto JSON generico.

    Se però poi il codice non deve fare nulla di specifico per determinati servizi e determinati campi che arrivano in formato JSON, non so neanche se ha senso farne il parsing piuttosto che passare la stringa così com'è: in breve, si da per scontato che siano dati "accompagnatori", perché qualora fossero informazioni di primo piano e rilevanti, da utilizzare nella business logic, allora andrebbero preferibilmente mappate su classi effettive con una struttura precisa.

    Ciao!
  • Re: Passare tipo anonimo variabile

    Grazie ma purtroppo mi da errore peccato perche c'eravamo quasi
    a quanto ho capito dentro usa reflection e non riesce a capire il tipo visto che rimane sempre JObject
    come posso fare? non c'e modo?
  • Re: Passare tipo anonimo variabile

    orecchione ha scritto:


    Grazie ma purtroppo mi da errore peccato perche c'eravamo quasi
    Quale errore? Con quale codice? Facendo che cosa?

    orecchione ha scritto:


    a quanto ho capito dentro usa reflection e non riesce a capire il tipo visto che rimane sempre JObject
    come posso fare? non c'e modo?
    "Non riesce a capire il tipo": stando alle premesse dovrebbe prescindere dal tipo, visto che il metodo CallService() - di cui non sappiamo nulla - accetta un object come parametro, quindi se si aspetta un tipo specifico, allora deve fare riferimento a quello; in alternativa, deve accettare tutto.

    Per il resto, è chiaro a grandi linee quello che si vuole fare, ma mancano tutti i dettagli specifici per poter capire come giungere a una risoluzione chiara ed efficace del problema: perché non ci sono classi per rappresentare queste diverse tipologie di servizi? come è fatto il metodo in questione che prende il parametro? perché non accetta qualsiasi oggetto come dovrebbe? se da errore, qual è l'errore che restituisce?

    Sono troppi gli aspetti opachi per fornire un suggerimento definitivo.
  • Re: Passare tipo anonimo variabile

    Grazie per la risposta
    non so molto del metodo perché mi viene fornito cosi quindi ho molti servizi che ognuno ha il suo tipo con le proprieta che li servono purtroppo l'errore non me lo ricordo ora
    negli esempi che mi hanno fornito ci sono i tipi anonimi e cambiano a secondo del servizio che chiamo e io vorrei poterli chiamare ad esempio dalla console o da un servizio web ad esempio scrivendo il servizio da chiamare e passando i dati che servono scrivendo ad esempio "messaggio|'Messaggio da Gigi','Ciao da Gigi'"
    se lo prevedo in compilazione posso fare uno switch o if [Code] //gli esempi fornito sono //CallService("Audio",new{Volume=10}); //CallService("Message",new{Title="My title", Text="Welcome", FontSize=10}); //e molti altri servizi con i loro dati e io potrei fare var comandi = "messaggio|'Messaggio da Gigi','Ciao da Gigi".split('|'); oppure var comandi = "audio|10".split('|'); var variabili = comandi[1].split(','); .... if (comandi[0]=="messaggio"){ callservice(comandi[0], new {Title=variabili[0],Text=variabili[1]}); } if (comandi[0]=="audio"){ callservice(comandi[0], new {Volume=Convert.ToDouble(variabili[0])}); } Ma devo scriverlo per ogni servizio che ha la sua struttura ma voglio evitare e mi chiedo se esiste un modo per scrivere il tipo anonimo con la stringa e poi trasformarlo in oggetto in modo da scrivere tipo [Code] var comandi ="audio|new {Volume=10}".split('|'); callservice(comandi[0], comandi[1].ToObject()) ovviamente ToObject() mi dovrebbe generare il tipo anonimo new {Volume=10}
  • Re: Passare tipo anonimo variabile

    Buongiorno
    Ho controllato facendo con jObject del tuo esempio nel link non da errore ma non fa nulla [Code] string json = "{ Title: 'Title', Message:'Welcome' }"; JObject msg = JObject.Parse(json); CallService("Message", msg ); invece facendo con il tipo anonimo funziona [Code] var msg= new { Title = "Title", Message = "Welcome" }; CallService("Message", msg ); un altra cosa che mi sbagliavo era che non funziona ma non da errori nemmeno facendo questo new { Title = "Title", Message = "Welcome", FontSize=10 } negli esempi non ce FontSize=10 era solo una prova nostra e reflection era una deduzione del collega

    grazie
  • Re: Passare tipo anonimo variabile

    La mia teoria è che il tuo collega abbia visto giusto. Infatti se provi a ricavare una proprietà tramite reflection dal tipo anonimmo la ottieni senza problemi, a patto che esista, diversamente se provi a farlo tramite JObject troverai solo le proprietà del tipo JObject, ma non quelle definite nella stringa.
    [Code] JObject jObject = JObject.Parse("{ Title: 'Title', Message: 'Welcome'}"); //var title = jObject.Title non corretto, il tipo è JObject e non possiede una proprietà di nome "Title" //Ma usando dynamic: dynamic dynamicJObj = jObject; //è possibile ricavare la proprietà Title var title = dynamicJObj.Title; //recuperando il tipo ovviamente otteniamo JObject in tutti i casi var type = jObject.GetType(); //di conseguenza non è possibile ricavare tramite reflection la propertyInfo da cui ricavare (o impostare) il valore var property = type.GetProperty("Title");//null type = dynamicJObj.GetType(); property = type.GetProperty("Title");//null //Altra situazione invece con il tipo anonimo var anonymous = new { Title = "Title", Message = "Welcome" }; //in cui è possibile ricavare il tipo type = anonymous.GetType(); //dal tipo si può ricavare la propertyInfo property = type.GetProperty("Title"); // e quindi il valore tramite reflection title = property.GetValue(anonymous); Se il motivo è questo, per come la vedo io hai una sola via di uscita, ovvero quella di compilare il tipo in runtime, anche se questo non è ne semplice, ne troppo bello e tanto meno performante, ma se necessario fa il suo dovere.
    Ti mostro come potresti fare, ma per farlo come si deve ti conviene studiarti bene l'argomento, in rete trovi tutto il necessario, e puoi partire da questa base:
    [Code] public static class StringExtension { public static dynamic ToDynamicObject(this string json, string typeName = "MyDynamicType") { MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig; AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("MyDynamicAssembly"), AssemblyBuilderAccess.Run); ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("MyDynamicAssemblyModule"); TypeBuilder dynamicType = dynamicModule.DefineType(typeName, TypeAttributes.Public); try { JObject o = JObject.Parse(json); o.Properties().ToList().ForEach(f => { var name = f.Name; var propertyType = (f.Value as Newtonsoft.Json.Linq.JValue).Value.GetType(); FieldBuilder field = dynamicType.DefineField("_" + name, propertyType, FieldAttributes.Private); PropertyBuilder property = dynamicType.DefineProperty(name, PropertyAttributes.None, propertyType, new[] { propertyType }); MethodBuilder getMethodBuilder = dynamicType.DefineMethod("get_value", getSetAttr, propertyType, Type.EmptyTypes); ILGenerator getIl = getMethodBuilder.GetILGenerator(); getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldfld, field); getIl.Emit(OpCodes.Ret); MethodBuilder setMethodBuilder = dynamicType.DefineMethod("set_value", getSetAttr, null, new[] { propertyType }); ILGenerator setIl = setMethodBuilder.GetILGenerator(); setIl.Emit(OpCodes.Ldarg_0); setIl.Emit(OpCodes.Ldarg_1); setIl.Emit(OpCodes.Stfld, field); setIl.Emit(OpCodes.Ret); property.SetGetMethod(getMethodBuilder); property.SetSetMethod(setMethodBuilder); }); } catch { return null; } return JsonConvert.DeserializeObject(json, dynamicType.CreateType()); ; } } Ho voluto utilizzare anche qui un estensione perché hai scritto che ti sarebbe piaciuto fare una cosa del genere: "comandi[1].ToObject()", ma mi sembrava più appropriato chiamarlo ToDynamicObject(), comunque puoi personalizzarlo come vuoi, nel caso chiedi pure.
    Così com'è la puoi usare in questo modo:
    [Code] var dynamicObject = "{ 'Title': 'Title', 'Message': 'Welcome'}".ToDynamicObject(); //in questo caso il typo ha il nome di default: "MyDynamicObject" type = dynamicObject.GetType(); //si possono recuperare le proprietà tramite reflection property = type.GetProperty("Title"); title = property.GetValue(dynamicObject); //oppure chiamando direttamente la proprietà, ma sarebbe inutile a quel punto fare tutto questo. title = dynamicObject.Title; //In questo caso ho specificato il nome che il tipo deve avere dynamicObject = "{'Volume':1.1}".ToDynamicObject("Audio"); double doubleValue = dynamicObject.Volume ; //si chiama: "Audio" type = dynamicObject.GetType(); é un peccato, con JObect puoi tranquillamente recuperare quasi tutto quello che serve come faresti con la reflection, ma purtroppo se non hanno utilizzato questo ma la reflection c'è poco da fare.
    Una curiosità: per caso questo metodo (CallService) con questo sistema ha a che fare con un famoso hub di domotica? Ho visto qualcosa di simile (non proprio come lo descrivi ma con nomi e logica simili tipo "media_player.volume_set") per trattare i servizi delle entità.
  • Re: Passare tipo anonimo variabile

    Buondì

    Sicuramente con la reflection si può fare tutto.. Nel tuo caso, però, puoi cambiare la "firma" del metodo?
    Perchè se potessi farla diventare un generic, poi secondo me ti funziona tutto senza codice troppo complesso.

    Tanto per intenderci, poter fare il metodo CallService<T>(string service, T data)

    Così facendo, il serializzatore JSON conosce il tipo oggetto, poichè è di tipo T, cosa che non riesce a fare quando usi un normale "object" poichè in quel caso usa proprio il tipo object e non quello reale.


    Ovvio che prevede il cambio della firma, però non dovrebbe creare problemi in fase di compilazione, poichè di fatto il tipo generico T può essere anche object
  • Re: Passare tipo anonimo variabile

    PiGi78 ha scritto:


    Buondì

    Sicuramente con la reflection si può fare tutto.. Nel tuo caso, però, puoi cambiare la "firma" del metodo?
    Perchè se potessi farla diventare un generic, poi secondo me ti funziona tutto senza codice troppo complesso.

    Tanto per intenderci, poter fare il metodo CallService<T>(string service, T data)

    Così facendo, il serializzatore JSON conosce il tipo oggetto, poichè è di tipo T, cosa che non riesce a fare quando usi un normale "object" poichè in quel caso usa proprio il tipo object e non quello reale.


    Ovvio che prevede il cambio della firma, però non dovrebbe creare problemi in fase di compilazione, poichè di fatto il tipo generico T può essere anche object
    Ciao PiGi78,
    mi sembra di capire che non abbia il controllo del codice del metodo, ma questo interessa anche me a prescindere, però non ho capito cosa intendi esattamente. T diventerebbe comunque JObject
    Ho pensato quello della firma fosse un escamotage , ma non sono riuscito a farlo funzionare. Ho provato questo ad esempio, ci sono tutti gli esempi, sia con tipo anonimo (però è necessario dichiararlo in compilazione), sia JObject e sia il tipo creato in runtime per poterli confrontare.
    Ora magari non ho capito cosa serve a orecchione, però a me interesserebbe capire se con il tuo suggerimento si può creare un tipo in runtime a partire da una stringa che funzioni con reflection per recuperare le info dalle proprietà. Se si potresti mostrarmi come?
  • Re: Passare tipo anonimo variabile

    best1904 ha scritto:


    T diventerebbe comunque JObject
    Se si usa ToDynamicObject() ovviamente sì, ma non è l'unico modo per deserializzare un oggetto partendo dal JSON.

    Ad esempio, si può usare anche JsonConvert.DeserializeAnonymousType<>() che ha il supporto per tipi anonimi, o il più generico JsonConvert.DeserializeObject<>(), tutti basati sui Generics e quindi capaci di supportare il tipo indicato.

    Sempre se ho capito bene il dubbio originale.

    Ciao!
  • Re: Passare tipo anonimo variabile

    Alka ha scritto:


    best1904 ha scritto:


    T diventerebbe comunque JObject
    Se si usa ToDynamicObject() ovviamente sì, ma non è l'unico modo per deserializzare un oggetto partendo dal JSON.

    Ad esempio, si può usare anche JsonConvert.DeserializeAnonymousType<>() che ha il supporto per tipi anonimi, o il più generico JsonConvert.DeserializeObject<>(), tutti basati sui Generics e quindi capaci di supportare il tipo indicato.

    Sempre se ho capito bene il dubbio originale.

    Ciao!
    Non so se ho capito male io o tu, ma non ha importanza con il mio quesito. Io parto dal presupposto, magari sbagliato, che l'errore che ha riscontrato orecchione possa essere causato dal fatto che dentro CallService possano ricavare informazioni tramite reflection, questo spiegherebbe perché magari con JObect "non funziona" mentre con il tipo anonimo si. Ma è solo una mia supposizione.
    Diciamo che in quel caso dovrebbe passare un tipo che possa lavorare con reflection. Ovviamente anche JObject lavora con reflection essendo un tipo, ma se si cercano le proprietà indicate dal json non si trovano.
    Con JsonConvert.DeserializeAnonymousType bisogna comunque dichiarare in compilazione la definizione con un tipo anonimo o comunque dichiarare una classe se usi JsonConvert.DeserializeObject, o sbaglio? Nel caso non mi sbagli tanto vale dichiarare direttamente il tipo anonimo e passarli quello. Nel caso mi sbagli mi piacerebbe capire come si fa, se fossi così gentile da mostrarmelo ti ringrazierei.
    Da quello che mi pare di capire orecchione è in una situazione in cui lui senza ricompilare nulla nel caso aggiungano "funzionalità" o senza prevedere tutti i tipi a priori, possa chiamare con stringa dei comandi passando dei valori tramite tipi che non sono previsti in compilazione da lui, ma che vengono letti, supposizione mia, tramite reflection dal sistema che espone il metodo CallService. Ma fintanto che non lo dice lui se funziona o no "ToDynamicObject()" non possiamo sapere se l'intuizione della reflection è corretta, ma poco importa, la discussione la trovo costruttiva.
    Nel caso serva solo la lettura/scrittura delle proprietà, JObect fa il suo lavoro egregiamente, puoi ricavare tutte le info necessarie, ma non tramite reflection purtroppo, quindi non sarebbe adattabile ad un sistema già esistente, per quanto sia opinabile visto che lavorerebbe molto bene con JObject. Sarebbe bello valutare bene se sia meglio reflection o usare JObject, ovviamente intendendo per ricavare le sole proprietà, per il resto senza reflection non si può, vedi ad esempio chiamare metodi.

    EDIT:
    Probabilmente intendevi che con ToDynamicObject() non è JObject.
  • Re: Passare tipo anonimo variabile

    Per i generics il gioco è semplice:
    • Crei il metodo col tipo generico
    • In compile time usi il metodo con qualsiasi oggetto che rispecchia i limiti del generico (ammesso che ci siano)

    Supponiamo di voler convertire in JSON un oggetto generico.
    Se usiamo "object", il serializzatore JSON lavora con object e fatica a trovare le nostre proprietà.
    Se usiamo il generic, il serializzatore JSON lavora con la classe ricevuta nel metodo, per cui trova le sue proprietà senza problemi.


    Vediamo un esempio pratico:
    
    
    // Lavoriamo col generic T
    string SerializeToJson<T>(T obj)
       where T : class
    {
       return JsonSerializer.Serialize(obj);
    }
    
    
    // Definiamo una classe
    class Classe1
    {
       public int Id { get; set; }
    
       public string Name { get; set; }
    }
    
    
    var x = new Classe1 { Id = 10, Name = "prova" };
    var jsonText = SerializeToJson(x); 
    
    
    In questo caso, il serializzatore lavora prendendo come argomento "T" che deve essere per forza una classe (vedi la clausola "where").
    Ricevendo in input un oggetto di tipo T (diverso da object), il sistema è in grado di lavorare con tutte le proprietà di T.
    Nell'esempio, richiamandolo passando un oggetto di tipo Classe1, il serializzatore diesce ad accedere alle proprietà Id e Name.

    Può andare bene per il vostro caso?

    Chiedo sia a orecchione (che ha aperto il post) che a best
  • Re: Passare tipo anonimo variabile

    A me è sembrato di capire che non volesse creare i tipi in compilazione, e il mio dubbio era su quello. Avevo capito che tu in qualche modo riuscissi a creare il tipo in runtime semplicemente con i generics, in quel caso sarei stato particolarmente interessato a capire come fare.
    Nel tuo esempio, di cui ti ringrazio comunque per la disponibilità, comunque crei il tipo in compilazione, parliamo della classe: "Classe1".
    A questo punto, visto che siete già in due ad aver capito che può creare i tipi in compilazione, credo di aver capito male io. Io non intendo creare l'oggetto in runtime, ma proprio creare il tipo in runtime, la differenza sta nel fatto che il tipo viene creato in compilazione e l'oggetto viene crerato in runtime a partire dal json, il modo suggerito da me (ToDynamicObject()) crea sia il tipo e sia il corrispondente oggetto in runtime in modo che può essere usato con tipi senza prevederli in compilazione. Un pò come fai con JObject ma su cui si può usare la reflection. In pratica per riprendere il tuo esempio posso fare questo senza dichiarare "Class1" (puoi vederlo in azione qui nella riga 14 qui):
    
    var class1 = "{ Id:10, Name:'prova'}".ToDynamicObject("Class1");
    var serJson = JsonConvert.SerializeObject(class1); //{"Id":10,"Name":"prova"}
    Type type = class1.GetType();//Type = Class1
    
    Spero di aver spiegato cosa intendevo io, ora rimane da capire se era anche quello che voleva orecchione o se per lui va bene dover dichiarare tutte le classi di tutti i servizi a priori.
    Quando dice questo:

    orecchione ha scritto:



    se lo prevedo in compilazione posso fare uno switch o if [Code] //gli esempi fornito sono //CallService("Audio",new{Volume=10}); //CallService("Message",new{Title="My title", Text="Welcome", FontSize=10}); //e molti altri servizi con i loro dati e io potrei fare var comandi = "messaggio|'Messaggio da Gigi','Ciao da Gigi".split('|'); oppure var comandi = "audio|10".split('|'); var variabili = comandi[1].split(','); .... if (comandi[0]=="messaggio"){ callservice(comandi[0], new {Title=variabili[0],Text=variabili[1]}); } if (comandi[0]=="audio"){ callservice(comandi[0], new {Volume=Convert.ToDouble(variabili[0])}); } Ma devo scriverlo per ogni servizio che ha la sua struttura ma voglio evitare e mi chiedo se esiste un modo per scrivere il tipo anonimo con la stringa e poi trasformarlo in oggetto in modo da scrivere tipo [Code] var comandi ="audio|new {Volume=10}".split('|'); callservice(comandi[0], comandi[1].ToObject()) ovviamente ToObject() mi dovrebbe generare il tipo anonimo new {Volume=10}
    Mi fa pensare che lui non voglia scrivere il tipo in compilazione, ma mi arrendo siete in due ad aver capito che non è un problema dichiarare il tipo in compilazione.
Devi accedere o registrarti per scrivere nel forum
12 risposte