On Thu, 2 Mar 2017 12:25:47 -0800 (PST)
Basile Starynkevitch <bas...@starynkevitch.net> wrote:

> This question is related to that one 
> <https://groups.google.com/d/msg/golang-nuts/nIshrMRrAt0/KSnwExJ4DAAJ> 
> (which gives more detailed motivation).
> 
> Assume I have some public type 
> Person = struct { 
>   PersName: string `json:"name"` 
>   PersPhone: int `json:"phone"` 
> }
> 
> I want to unmarshal (i.e. JSON decode) some JSON, with e.g. the
> following rules
> 
> a scalar JSON integer is decoded as some Go int64. So Json 123 *⇾* Go 
> int64(123) etc....
> 
> a scalar JSON string is decoded as some Go string. So JSON "abc" *⇾*
> Go string("abc") etc...
> 
> a JSON object is decoded according to its *first* name:
> 
>    - when that name is "transl" the corresponding value is a number,
> and the decoding is the closure of that mathematical translation 
>    <https://en.wikipedia.org/wiki/Translation_%28geometry%29>. So the
> JSON { "transl" : 1 }  *⇾* Go closure  func(x int) { return x + 1}
> (of course the 1 would actually be the binding of a captured variable
> y in some closure func(x int) { return x + y }).
>    - when that name is "strings" the corresponding value is a JSON
> array of strings, and its decoding is the slice of such strings. So
> the JSON { "strings" : [ "a", "b" ] } *⇾* Go strings slice []string
> {"a","b"}
>    - when that name is "name", there is a second name "phone", and
> its decoding is the appropriate instance of Person. So the JSON
> { "name" : "John Smith", "phone": 1234567 }
>    *⇾* Go Person{PersName:"John Smith", PersPhone: 1234567}
> 
> I don't care for other cases or forms of JSON (in particular other
> forms of JSON objects). The decoding (or unmarshaling) could fail (or
> give some other value).

Something like this?

----------------8<----------------
    package main
    
    import (
        "encoding/json"
        "errors"
        "fmt"
    )
    
    var ErrMissingPhone = errors.New(`Missing JSON object field: phone`)
    var ErrEmpty = errors.New(`Missing JSON object field: must specify
one of: ` + `"transl", "strings" or "name"`)
    
    type Person struct {
        PersName  string
        PersPhone string
    }
    
    type MyValue struct {
        Value interface{}
    }
    
    func (o *MyValue) UnmarshalJSON(b []byte) (err error) {
    
        var d struct {
                Transl  *int
                Strings []string
                Name    *string
                Phone   *string
        }
    
        err = json.Unmarshal(b, &d)
        if err != nil {
                return
        }
    
        if d.Transl != nil {
                o.Value = func() int {
                        return *d.Transl + 1
                }
                return
        }
    
        if d.Strings != nil {
                o.Value = d.Strings
                return
        }
    
        if d.Name != nil {
                if d.Phone == nil {
                        err = ErrMissingPhone
                        return
                }
                o.Value = Person{
                        PersName:  *d.Name,
                        PersPhone: *d.Phone,
                }
                return
        }
    
        err = ErrEmpty
        return
    }
    
    const input = `
    [
        {
                "transl": 42
        },
        {
                "strings": [ "a", "b" ]
        },
        {
                "name": "John Doe",
                "phone": "123"
        }
    ]
    `
    
    func main() {
        var out []MyValue
    
        err := json.Unmarshal([]byte(input), &out)
        if err != nil {
                panic(err)
        }
        fmt.Printf("%#v\n", out)
    }
----------------8<----------------

(Playground link: <https://play.golang.org/p/hgjNUqudRN>)

You will probably want to post-process the result to extract actual
values out of those MyValue "containers".

Two other points:

 * Instead using a helper type "data" which contains pointers to
   values to be able to distinguish between the zero values of those
   types and the "field was not set" cases you could unmarshal your
   object into a value of type map[string]interface{} -- each object's
   field would produce an entry in that map with the field's name
   being the entry's key and the field's value being the entry's value.

   You would then test whether a particular field was specified by
   using something like

     if v, ok := thatmap[fieldname]; ok {
       // Act on fieldname having been set
     }

 * The code does not check for multiple "designator" fields being set
   at the same time -- say, both "transl" and "strings" are set.

   You might want to specifically check for this case.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to