2015-04-06 45 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