2013-06-22 11 views
9

Sto sperimentando con la libreria di json4s (basata su lift-json). Una delle cose che vorrei fare è analizzare una stringa JSON in un AST e quindi manipolarla.Come manipolare JSON AST in Scala

Per esempio, vorrei spostare un campo (inserire il campo nell'AST se non esiste o aggiornare il suo valore se lo fa).

Non sono stato in grado di trovare come farlo nella documentazione. Sperimentando con i metodi disponibili, ho trovato il seguente, che funziona, ma si sente goffo.

import org.json4s._ 
import org.json4s.JsonDSL._ 
import org.json4s.native.JsonMethods._ 

object TestJson { 
    implicit val formats = DefaultFormats 

    def main(args: Array[String]): Unit = { 
    val json = """{"foo":1, "bar":{"foo":2}}""" 
    val ast = parse(json).asInstanceOf[JObject] 

    println(upsertField(ast, ("foo" -> "3"))) 
    println(upsertField(ast, ("foobar" -> "3"))) 
    } 

    def upsertField(src:JObject, fld:JField): JValue = { 
    if(src \ fld._1 == JNothing){ 
     src ~ fld 
    } 
    else{ 
     src.replace(List(fld._1), fld._2) 
    } 
    } 
} 

Cosa non mi piace per molti motivi:

  1. Avere per lanciare in modo esplicito i risultati di parse(json)-JObject
  2. Il risultato della funzione upsertField è un JValue, che dovrò rifusione se Voglio manipolare ulteriormente l'oggetto
  3. La funzione upsertField si sente semplicemente poco elegante
  4. Non funziona per i campi che non si trovano al livello superiore della gerarchia

C'è un modo migliore per trasformare AST?

EDIT: come una soluzione al problema, sono riuscito a convertire il mio JSON per Scala classi regolari, e manipolarli con le lenti (Using Lenses on Scala Regular Classes)

+0

ciò che AST stand for? –

+1

@QuyTang AST sta per "abstract syntax tree" – Eduardo

+0

Grazie @Eduardo –

risposta

12

C'è la funzione di unione che crea o sovrascrive un campo. È inoltre possibile aggiornare i campi che non si trovano al livello radice dell'albero.

import org.json4s._ 
import org.json4s.JsonDSL._ 
import org.json4s.jackson.JsonMethods._ 

object mergeJson extends App { 

    val json = 
    """ 
     |{ 
     | "foo":1, 
     | "bar": { 
     | "foo": 2 
     | } 
     |} 
     |""".stripMargin 

    val ast = parse(json) 

    val updated = ast merge (("foo", 3) ~ ("bar", ("fnord", 5))) 

    println(pretty(updated)) 

    // { 
    // "foo" : 3, 
    // "bar" : { 
    //  "foo" : 2, 
    //  "fnord" : 5 
    // } 
    // } 

} 
0

Quando mi è stato l'attuazione di alcune molto specifiche diff JSON con ascensore JSON I Ho usato molte funzioni ricorsive per arrivare al jpath dove ho bisogno di modificare il valore, e il json modificato è stato costruito quando la ricorsione è "compressa". Dopo tutto, LiftJson è immutabile. Hai citato gli obiettivi come un altro approccio, che è di per sé molto interessante. Ma il mio preferito attuale è la libreria play-JSON che sta lavorando come un fascino in una situazione in cui è necessario fare JSON-to-JSON trasformazione:

da Mandubian Blog:

val gizmo2gremlin = (
    (__ \ 'name).json.put(JsString("gremlin")) and 
    (__ \ 'description).json.pickBranch(
     (__ \ 'size).json.update(of[JsNumber].map{ case JsNumber(size) => JsNumber(size * 3) }) and 
     (__ \ 'features).json.put(Json.arr("skinny", "ugly", "evil")) and 
     (__ \ 'danger).json.put(JsString("always")) 
     reduce 
) and 
    (__ \ 'hates).json.copyFrom((__ \ 'loves).json.pick) 
) reduce 

Yummy Caratteristiche: tutti i trasformatori sono combinatori che possono essere combinati insieme, convalida, supporto senza forma, marshalling automatico di classi caso con override implicite, libreria autonoma.

1

Vorrei anche che vi dia la versione SON of JSON:

import nl.typeset.sonofjson._ 

val json = parse("""{ "foo" : 1, "bar" : { "foo" : 2 } }""") 

// or, perhaps a little easier 
val json = obj(foo = 1, bar = obj(foo = 2)) 

json.foo = "3" 
json.foobar = "3" 
+0

Grazie. Farò una prova nel mio prossimo progetto! – Eduardo