Come trasmettere la gerarchia degli oggetti quando l'oggetto viene convertito in json per gli aggiornamenti?

2

Ho alcune classi serializzate tramite frugale (questa è un'estensione open source di Thrift ). Il motivo principale per cui questo è rilevante è che il processo di serializzazione è fuori dal mio controllo e deve supportare più lingue, quindi non posso facilmente modificarlo.

Immagina che le mie classi assomiglino a:

class response_component:
    value = 'string value'
    another_value = 4
    another_string = 'another string'

class the_response:
    contained = response_component

Questo viene serializzato in json simile a:

{"response": {"the_response": {"value": "string value", "another_value": 4, "another_string": "another string"}}}

Questo può essere interpretato e ricreato dal framework di messaggistica sottostante in classi sul lato destinatario. Quello che mi interessa fare è avere il middleware apportare modifiche agli attributi sottostanti.

Ad esempio, potrei voler cambiare il valore "another_string" nell'esempio di risposta qui per "ciao".

La difficoltà che ho è capire come denotare questo riferimento in modo tale che sia significativo all'interno del contesto json.

Posso fare qualcosa che sembra un hokey come articolare il percorso assoluto attraverso le classi, come RESPONSE.contained.another_string e poi nel processo di deserializzazione decomprimendo questo (o, in python, facendo una serie di getattr finché non trovo l'ultimo valore. Questo potrebbe sembrare, in Python:

r = the_response()
print(r.contained.another_string)
path = 'the_response.contained.another_string'

v = None
paths = path.split('.')
for p in paths[1:-1]:
    if not v: v = r
    print(p)
    v = getattr(v, p)


setattr(v, paths[-1], 'hello')

print(r.contained.another_string)

comunicato tramite json come:

{"modified_value": {"key": "the_response.contained.another_string", "val": "hello"}}

Tuttavia, io veramente non apprezzo l'approccio del percorso e spero in un modo diverso e migliore per comunicare dove modificare i valori.

    
posta enderland 27.10.2017 - 17:32
fonte

1 risposta

1

Se si dispone di un documento JSON e si desidera modificare chiavi specifiche, non è possibile specificare i percorsi assoluti. Questo cambia quando puoi deserializzare in classi sotto il tuo controllo, perché puoi applicare il modello composito / visitatore.

Nel caso JSON puro in cui le voci non si auto descrivono oltre i loro nomi di chiavi, hai solo i nomi delle chiavi disponibili per navigare nella struttura dei dati. Pertanto, nessuna soluzione, ma l'utilizzo di percorsi assoluti è possibile.

C'è un RFC 6901 "JSON Pointers" che fornisce una sintassi per questi percorsi, con le implementazioni Python disponibili. Questo è approssimativamente XPath-ish, ma può riguardare solo un singolo elemento - non ci sono caratteri jolly. Ciò rende JSON Pointers inadatto ad es. quando si desidera modificare ogni voce in una matrice. Potrebbe quindi essere sensato implementare la tua sintassi più flessibile, come hai abbozzato nella tua domanda.

Con le classi personalizzate, puoi aggiungere un comportamento a queste classi che semplifica notevolmente la ricerca delle voci da modificare. Ciò richiede due modifiche:

  • Tutte queste classi implementano il pattern Visitor in modo che un'istanza definita esternamente possa essere applicata all'istanza, ma solo a un tipo specifico.
  • Ogni classe sa quali dei suoi membri sono anche visitabili e distribuisce l'operazione ai suoi figli - un'istanza del Pattern composito.

Se non ti piacciono i pattern, tutto questo può anche essere implementato su una base abbastanza buona usando le funzioni di riflessione.

Ciò richiede che il tuo modello di dati abbia un chiaro concetto di tipi in modo che le modifiche possano essere limitate ai tipi corretti.

La soluzione Visitatore / Composito sarebbe simile a questa:

class response_component:
  ...
  def accept_visitor(self, visitor):
    visitor.visit_response_component(self)

class the_response:
   ...
   def accept_visitor(self, visitor):
     visitor.visit_the_response(self)
     # distribute the operation to relevant childs
     self.contained.accept_visitor(visitor)

class SetAnotherString(object):
   def __init__(self, value):
     self.value = value

   def visit_the_response(self, r): pass
   def visit_response_component(self, c): c.another_string = self.value

json_data.accept_visitor(SetAnotherString('hello'))

Poiché le tue azioni sembrano essere piuttosto dinamiche, potrebbe essere opportuno sostituire la spedizione del metodo del visitatore con una ricerca nel dizionario:

class response_component:
  ...
  def accept_visitor(self, visitor):
    visitor(self, 'response_component')

class the_response:
  ...
  def accept_visitor(self, visitor):
    visitor(self, 'the_response')
    # distribute the operation to relevant childs
    self.contained.accept_visitor(visitor)

class SetValues(object):
  def __init__(self, changes_for_class: Dict[str, Dict[str, Any]]):
    self.changes_for_class = changes_for_class

  def __call__(self, instance, classname):
    try: changes = self.changes_for_class[classname]
    except KeyError: return
    for k, v in changes.items():
      setattr(instance, k, v)

json_data.accept_visitor(SetValues({
    'response_component': dict(another_string='hello'),
}))

Rispetto al visitatore con doppia spedizione, questi aggiornamenti sono più facili da rappresentare in modo indipendente dalla lingua. Ma rispetto a JSON Pointers, entrambi gli approcci dei visitatori devono cercare nel documento completo tutte le istanze che devono essere aggiornate, mentre il puntatore risolve solo un singolo elemento. Potrebbe essere possibile creare un linguaggio di query ad hoc che combini entrambi questi approcci, ad es. utilizzando un puntatore JSON per specificare un prefisso della struttura dati in cui deve iniziare la ricerca basata sui visitatori.

    
risposta data 15.01.2018 - 13:14
fonte

Leggi altre domande sui tag