2015-04-06 91 views
1

我正在處理遺留系統返回JSON與嵌套的結構和一些可選字段(和隨機順序)。像這樣的東西:處理嵌套的JSON結構與自定義解組器

type A struct { 
    /* simple struct, can be unmarshalled normally */ 
    AF1 string `json:"AF1"` 
} 

type B struct { 
    /* simple struct, can be unmarshalled normally */ 
    BF1 string `json:"BF1"` 
} 

type X struct { 
    Things []A `json:"things"` /* mandatory */ 
    Thangs []B `json:"thangs"` /* mandatory */ 
    /* some individual string values may or may not appear, eg: 
    Item1 string 
    Item2 string 
    */   
} 

如果項目[12]確實出現,我想把它們藏在地圖或類似的地方。

是否有任何優雅的方式來解組X?有沒有辦法爲X編寫一個自定義UnmarshalJSON函數(用於處理選項字符串字段),然後將它交給A和B的默認JSON解組器?

+0

如果我理解下面的評論,請編輯的問題(可能帶有示例JSON輸入)來明確輸入可以具有任何鍵/值,而不僅僅是「Item1」(例如,它聽起來像是「{/ * A和B的* /,」somekey「:」someval「,」otherkey 「:42}'可能是有效的輸入 – 2015-04-06 03:20:01

+0

嗨,我不確定你是否看到過這個,或者它是否有幫助(這就是爲什麼我只寫評論,也許有人在這個問題上磕磕碰碰會覺得這很有用),但一些偉大的東西有關JSON和Go的內容將在本次演講中討論:https://www.youtube.com/watch?v = YgnD27GFcyA 看來你想編寫自己的Unmarshall for struct X,因爲他會討論如果我理解你的問題 – user3591723 2015-04-06 06:27:02

回答

0

如果我理解正確的問題,從您的其他評論,然後 輸入可能包含任何任意未知名額外的字段(和類型?) 和你想/這些需要訪問。 如果只是爲了以後重新編組,那麼json.RawMessage類型將是有趣的。

理想encoding/json將有一個特殊的標記 (如",any"encoding/xml標籤) 會自動收集任何多餘/未引用JSON物品放入或者是 map[string]interface{}map[string]json.RawMessage場。 然而,我找不到任何這樣的功能,也沒有找到一個明顯的方式來模仿匿名結構(但我沒有很努力)。

編輯:有an open issue in the Go project for this feature。很顯然,一個變更已經提交併部分回顧了Go 1.2,但最終沒有被接受。

做不到這一點,有幾個方法,你可以做的正是你有什麼建議, 作出X定製的(聯合國)和編組回調到JSON包處理[]A[]B

下面是一個快速拋出的例子, 可能有更好/更清晰/更安全的方法來做到這一點。 (在本例中,A和B可以是任意複雜的,也許包含本身具有自定義(UN)編組方法的類型。)

package main 

import (
    "encoding/json" 
    "fmt" 
) 

type A struct { 
    AF1 string 
} 

type B struct { 
    BF1 string 
} 

type X struct { 
    Things []A 
    Thangs []B 

    // Or perhaps json.RawMessage if you just 
    // want to pass them through. 
    // Or map of string/int/etc if the value type is fixed. 
    Extra map[string]interface{} 
} 

// Marshal Way 1: call unmarshal twice on whole input 

type xsub struct { 
    Things []A `json:"things"` 
    Thangs []B `json:"thangs"` 
} 

func (x *X) _UnmarshalJSON(b []byte) error { 
    // First unmarshall the known keys part: 
    var tmp xsub 
    if err := json.Unmarshal(b, &tmp); err != nil { 
     return err 
    } 

    // Then unmarshall the whole thing again: 
    var vals map[string]interface{} 
    if err := json.Unmarshal(b, &vals); err != nil { 
     return err 
    } 

    // Everything worked, chuck the map entries for 
    // "known" fields and store results. 
    delete(vals, "things") 
    delete(vals, "thangs") 
    x.Things = tmp.Things 
    x.Thangs = tmp.Thangs 
    x.Extra = vals 
    return nil 
} 

// Way 2: 

func (x *X) UnmarshalJSON(b []byte) error { 
    // Only partially decode: 
    var tmp map[string]json.RawMessage 
    if err := json.Unmarshal(b, &tmp); err != nil { 
     return err 
    } 

    // Now handle the known fields: 
    var things []A 
    if err := json.Unmarshal(tmp["things"], &things); err != nil { 
     return err 
    } 
    var thangs []B 
    if err := json.Unmarshal(tmp["thangs"], &thangs); err != nil { 
     return err 
    } 

    // And the unknown fields. 
    var extra map[string]interface{} 

    // Either: 
    if true { 
     // this has more calls to Unmarshal, but may be more desirable 
     // as it completely skips over the already handled things/thangs. 
     delete(tmp, "things") 
     delete(tmp, "thangs") 
     // If you only needed to store the json.RawMessage for use 
     // in MarshalJSON then you'd just store "tmp" and stop here. 

     extra = make(map[string]interface{}, len(tmp)) 
     for k, raw := range tmp { 
      var v interface{} 
      if err := json.Unmarshal(raw, &v); err != nil { 
       return err 
      } 
      extra[k] = v 
     } 
    } else { // Or: 
     // just one more call to Unmarshal, but it will waste 
     // time with things/thangs again. 
     if err := json.Unmarshal(b, &extra); err != nil { 
      return err 
     } 
     delete(extra, "things") 
     delete(extra, "thangs") 
    } 

    // no error, we can store the results 
    x.Things = things 
    x.Thangs = thangs 
    x.Extra = extra 
    return nil 
} 

func (x X) MarshalJSON() ([]byte, error) { 
    // abusing/reusing x.Extra, could copy map instead 
    x.Extra["things"] = x.Things 
    x.Extra["thangs"] = x.Thangs 
    result, err := json.Marshal(x.Extra) 
    delete(x.Extra, "things") 
    delete(x.Extra, "thangs") 
    return result, err 
} 

func main() { 
    inputs := []string{ 
     `{"things": [], "thangs": []}`, 

     ` 
{ 
    "things": [ 
    { 
     "AF1": "foo" 
    }, 
    { 
     "AF1": "bar" 
    } 
    ], 
    "thangs": [ 
     { 
      "BF1": "string value" 
     } 
    ], 
    "xRandomKey":  "not known ahead of time", 
    "xAreValueTypesKnown": 172 
}`, 
    } 

    for _, in := range inputs { 
     fmt.Printf("\nUnmarshal(%q):\n", in) 
     var x X 
     err := json.Unmarshal([]byte(in), &x) 
     if err != nil { 
      fmt.Println("unmarshal:", err) 
     } else { 
      fmt.Printf("\tas X: %+v\n", x) 
      fmt.Printf("\twith map: %v\n", x.Extra) 
      out, err := json.Marshal(x) 
      if err != nil { 
       fmt.Println("marshal:", err) 
       continue 
      } 
      fmt.Printf("\tRemarshals to: %s\n", out) 
     } 
    } 
} 

Run on Playground

0

根據json.Unmarshal(...)中的JSON對象類型聲明項目1/2爲map[string]interface{}。也

type X struct { 
    // ... 
    Item1 string map[string]interface{} 
    Item2 string map[string]interface{} 

注意,如果一個字段名的JSON鍵值名(不區分大小寫)匹配,那麼就沒有必要包括json:"..."名稱標記爲它:如果他們缺少將它們簡單設置爲nil

type A struct { 
    AF1 string // Will look for keys named "AF1", "af1", etc. 
} 
+0

感謝您的迴應!問題是我不知道項目[12 ...]先驗 - 基本上這些是遺留系統插入的隨機鍵/值對,但我關心接收。 (對於沒有提前明確表示抱歉。) – Michael 2015-04-06 02:48:38