decode unknown CBOR type with set of known types

466 views
Skip to first unread message

David Stainton

unread,
Jul 8, 2020, 3:44:36 PM7/8/20
to golang-nuts

Greetings,

Similar questions have been asked in the past and I've read those posts... and I believe I'm asking something different here.

I'm using length prefixed CBOR over unix domain socket as my wire format. However the reader doesn't known which CBOR type it needs to decode.
I'd like a mechanism similar to the golang switch type assertion where a known set of types are specified.

Before you go and tell me to use the empty interface let me explain that certainly this works:

var v interface{}
err = cbor.Unmarshal(out, &v)

It works for some definitions of "works". It doesn't tell me what type it is but it does allow me to access each struct field by name in the map of type: map[interface {}]interface {}

The reason this is not good enough is that it doesn't concretely tell me the type!
Of course I could go ahead and add a struct field called TypeName and set it's value to a string encoding the name. But that feels wrong and I'd feel bad after writing it.

I've also tried to ask this question on a ticket here:

Is the least incorrect solution to iteratively try cbor.Unmarshal with all the types in the known set of possible types until one of them works?

Certainly this problem isn't specific to CBOR. Am I asking the question incorrectly here?


Sincerely,
David

Jonathan Amsterdam

unread,
Jul 8, 2020, 5:55:26 PM7/8/20
to golang-nuts
My first question would be, is the type encoded into the CBOR blob somehow? Like by a tag? 

If structs are encoded as maps, with no type information, then the input doesn't have enough information to do what you want. How could the blob (using JSON notation)

   {"X": 1, "Y": 2}

know whether it should decode into

    type Point struct { X, Y int}

or 

    type Chromosome {X, Y int}

?

You have to encode the type somehow. If you control the encoding process and you're encoding a fixed set of types that you want to distinguish at decode time,
you can do something like this:

  type (
      This ...
      That ...
      TheOther ...

      Thing struct {
         This *This
         That *That
         TheOther *TheOther
     }
 )

To encode a `This` named `t`, actually encode `Thing{This: &t}`. When you decode that into a `Thing`, only the `This` field will be non-nil, so you know you've got a `This`.

(Disclaimer: I know this technique works with the encoding/json package; I can't guarantee it works with the cbor package you're using because I'm not familiar with it,
but if it behaves like encoding/json, you should be OK.)

Brian Candler

unread,
Jul 9, 2020, 5:25:00 AM7/9/20
to golang-nuts
By "unknown type", do you mean you want to unmarshal this CBOR map into a struct?

You need some way of identifying what the data is semantically, otherwise it might unmarshal successfully into multiple different structs.

I believe the CBOR native way of doing this is with "tags": https://tools.ietf.org/html/rfc7049#section-2.4

David Stainton

unread,
Jul 9, 2020, 12:56:07 PM7/9/20
to golang-nuts

Hi Jonathan,

Thanks for the suggestion. I'm going to take this approach of using a wrapping struct like in your example.

Although it might be possible to solve this problem with CBOR tags this seems more difficult and possibly not supported by the existing go CBOR libraries.

Cheers,
David

David Stainton

unread,
Jul 10, 2020, 10:55:54 PM7/10/20
to golang-nuts

Hi,

I've been informed of how exactly to use the CBOR tag solution by the author of one of the cbor.io recommended CBOR libraries:


This solution is clearly a bit nicer than the wrapping struct solution but it's specific to the given CBOR library whereas the wrapping struct solution would likely work with any serialization library.
Not that Brian and Jonathan both suggested this solution... however it's very helpful to see this code example of how exactly to do it since it wasn't immediately obvious from reading the API docs of the various CBOR libraries.
I shall quote the author's code sample herein:
"""

// Create TagSet with tag number and Go type
tags := cbor.NewTagSet()
err := tags.Add(
    cbor.TagOptions{EncTag: cbor.EncTagRequired, DecTag: cbor.DecTagRequired},
    reflect.TypeOf(MyType{}),    // your custom type
    MyTypeCBORTagNumber,         // CBOR tag number for your custom type
)

// Create decoding mode with TagSet	
dm, err := cbor.DecOptions{}.DecModeWithTags(tags)

// Decode unknown CBOR blob
var v interface{}
err = dm.Unmarshal(b, &v) 
  
// Use type switch to handle different types
switch v := v.(type) {
case MyType:  // custom type
    ...
default:
    ...
}

"""
Reply all
Reply to author
Forward
0 new messages