-
Notifications
You must be signed in to change notification settings - Fork 210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unmarshalling of nested structs #99
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed, please reply here (e.g.
|
I signed it! |
CLAs look good, thanks! |
hello @Slemgrim , I was having the same issues and got into your solution, I'm encountering however a few problems and before diving deep into your (and google's) code, I might use some feedback to know how are you using the tool. There are two main problems I'm encountering: problem 1 (tl;dr: how do you use the tags?)Do you tag an attribute with the json or jsonapi tag for a nested struct (or well, an attribute of the main jsonapi object which is a struct)?
when marshalling, the fields of the If I use the json tag instead, everything seems smooth marshalling, but the unmarshalling fails, it basically skips the attributes and initialises them to their default empty value. problem 2If I set Thanks for your solution anyway, it is saving me some headaches already, hope to hear from you soon ;). |
@dev-mush you're right. i don't see a reason why we should use jsonapi annotation inside nested structs. I haven't thought about that before. I try to update the PR in the next days. Can you create a separate Issue for the second problem? |
…r made sense to use json:api annotation in nested structs
@dev-mush i changed the PR to work with json annotation instead of json:api. Does the solution work for you? |
we're waiting for google/jsonapi to implement nested structs or to accept this PR google/jsonapi#99 We need nested structs for the upcoming mail endpoint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this PR. It offers some much needed in addition to its intent. Can you add more informative test failures? Like expected x got y instead of just, it failed.
@Slemgrim I'd really like to merge this, especially because it introduces a lot of much needed cleanup. A couple of questions,
I'd like to merge this asap so that we can avoid conflicts with other PRs. Thanks in advance, Sam |
tests should have an expected outcome vs. an actual outcome to make them easier to debug
@shwoodard I changed the tests to display expected vs. actual outcome. About the support for nested ptr: |
@Slemgrim I'll look into it. The key is the use of the |
I opened a PR to @Slemgrim's branch that makes nested struct pointers work for me: mrowdy#3 |
to aid in troubleshooting. Before: ``` Pointer type in struct is not supported ``` After: ``` jsonapi: Can't unmarshal true (bool) to struct field `Name`, which is a pointer to `string` ```
We're working on reviewing this CL now. We're going to merge this before any others. Everyone else who has open PR's please rebase on top of this branch or wait for it to be merged within a week or two. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix all lines to 80 chars. Thanks for the much needed cleanup help!
request.go
Outdated
// ErrInvalidType is returned when the given type is incompatible with the expected type. | ||
ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation. | ||
) | ||
|
||
// ErrUnsupportedPtrType is returned when the Struct field was a pointer but | ||
// the JSON value was of a different type | ||
func ErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refactor this so as to use the error
interface, the standard Go pattern for errors that have state.
- Create a public/exported
struct
called calledErrUnsupportedPtrType
with private (lower-case) fieldsrf
,t
, andstructField
as members. - Implement
func (eupt ErrUnsupportedPtrType) Error() string
to complete theerror
interface, See, https://golang.org/src/builtin/builtin.go?h=error#L255. - Create a private/unexported function called newErrUnsupportedPtrType and take the formal params that are listed above,
reflect.Value
,reflect.Type
,reflect.StructField
and returns aErrUnsupportedPtrType
, for convenience. - Use
return newErrUnsupportedPtrType(...)
in place of your exported function here.
request.go
Outdated
@@ -548,3 +374,239 @@ func assign(field, value reflect.Value) { | |||
field.Set(reflect.Indirect(value)) | |||
} | |||
} | |||
|
|||
func unmarshalAttribute(attribute interface{}, args []string, structField reflect.StructField, fieldValue reflect.Value) (value reflect.Value, err error) { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove whitespace.
request.go
Outdated
if kind.String() != "" && kind.String() != typeName { | ||
typeName = fmt.Sprintf("%s (%s)", typeName, kind.String()) | ||
} | ||
return fmt.Errorf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you do the change described above, you'll need to change this to fmt.Sprintf
to return string
.
request.go
Outdated
@@ -118,6 +129,7 @@ func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) { | |||
} | |||
|
|||
func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove whitespace
request.go
Outdated
} | ||
|
||
func handleTime(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove whitepace.
request.go
Outdated
return numericValue, nil | ||
} | ||
|
||
func handlePointer(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value, structField reflect.StructField) (reflect.Value, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
80 chars, etc
request.go
Outdated
func handleStruct(attribute interface{}, args []string, fieldType reflect.Type, fieldValue reflect.Value) (reflect.Value, error) { | ||
model := reflect.New(fieldValue.Type()) | ||
|
||
var er error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove
request.go
Outdated
|
||
var er error | ||
|
||
data, er := json.Marshal(attribute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename err and update all below.
@Slemgrim, Thank you so much for your contributions! I look forward to getting this merged soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shwoodard i changed all the requested things except for the switch. Maybe you can spot why it doesn't work if the two primitive types are added.
Thanks you for the feedback!
Awesome! Looking forward to this! |
I look forward to this PR being merged soon as well! Greatly needed here, good job! |
Awesome work, thank you! Is there an estimate on when this could be merged? I'd love to be able to use this. |
Hoping it will be merged soon too! 🙏🏻 |
@shwoodard is there anything i can do to get this merged soon? |
Looking forward to this being merged :) |
@shwoodard and @aren55555 I would like to join the list of people that would be really happy if this PR can get merged! It seems the authors and yourselves have given it quite some attention, so would be really awesome if you could give it a last push in order to merge the PR 😃Thanks! |
@shwoodard and @aren55555 sorry to bother you, but is there anyone who could give this project some TLC? It seems the last activity from any of it's maintainers is about 3 months ago, while there are a few very valuable PRs pending that solve quite some issues and/or use cases. So would be really great if someone could review and/or merge a few of them so we don't end up will everyone having it's own custom fork and lose the ability to share back 😏Thanks! |
Maybe if the current maintainers are too busy to tend to this project, perhaps they might consider adding a few more committers? |
Any update on this? |
Anything that can be done to move this forward? |
models_test.go
Outdated
} | ||
|
||
type Team struct { | ||
Name string `json:"name"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've read the comment @dev-mush left.
I think all these tags (even for a nested resource) should all be jsonapi
rather than json
. Why? What if you want to support serializations for both JSON and JSONAPI; you'd want separate tags for each. I'd recommend just using jsonapi:"attr,names"
even for these nested struct's fields.
The rest of this PR seems OK to me. @shwoodard perhaps you can also take a quick look?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aren55555 according to the spec attributes need to be valid json but can't be a json:api object since they are not allowed to have links or relationships.
Maybe I get this totaly wrong but this would allow the following:
{
"data": {
"id": "1324",
"type": "article",
"attributes": {
"title": "The best article",
"author": {
"id": "18",
"type": "author",
"attributes": {
"name": "Slemgrim"
}
}
}
}
}
where you also could do this:
{
"data": {
"id": "1324",
"type": "article",
"attributes": {
"title": "The best article",
}
},
"relationships": {
"author": {
"data": {"id": "18", "type": "author"}
}
},
"included": [{
"id": "18",
"type": "author",
"attributes": {
"name": "Slemgrim"
}
}]
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the unmarshal/marshal not just return an error if anything other than an attr is serialized/deserialized? Or it could ignore any tag that was not an attr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Slemgrim bump on question from @aren55555 . If we can resolve this, we'll merge this immediately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aren55555 @shwoodard I tried to look into this today. The changes required to allow "jsonapi" marshalling for nested attributes, seem non-trivial with the current architecture. At least non-trivial to me.
I'm happy to spend my time on this if someone could give me an example where this would be needed.
The jsonapi spec does not allow the fields "id, type, relationship, links" as attribute names. In other words: it doesn't allow to have a resource inside a resource. Allowing to tag the attributes of a non jsonapi resource with jsonapi tags would be missleading. It's like saying: "hey, you can name this json value jsonapi even though it will always be a normal json"
If you insist on this without further explanation then maybe one of the other contributers of this PR are willing to implement this. (@msabramo)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the solution be to validate that id, type, and links are missing from nested structs? Maybe we should allow relationships and process the data to be in the "included" part of the response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @aren55555 that we should use jsonapi for all properties serialized or deserialized by this lib.
Any more updates on this? We really need this :( |
Use jsonapi struct tags for nested attrs
I'm ready to merge the PR. I need all contributors to confirm CLA(s). @msabramo I think you're consent may be missing. |
* Change TLS certificate `name` attribute to be optional Custom TLS certificate without names default to the certificates common name or first SAN entry * Not use uint pointers in ListPrivateKeysInput struct This was causing problems when trying to use the page[number] parameter so I brought it more in line with the other methods. * Change TLS Activation `configuration` attribute to be optional (#28) Activations without the configuration specified default to the default configuration * Add TLS domains endpoint support * Drop use of reflection to build request parameters * Add documentation * TLS Subscriptions endpoints (#30) * Add ListTLSSubscriptions function * Add CreateTLSSubscriptions function * Add GetTLSSubscriptions function * Add DeleteTLSSubscriptions function * Add integration test with all endpoints * Add TLS Authorizations to TLS Subscriptions (#31) I had a lot of trouble deserialising the API response into appropriate structs. Initially I could only get it to work by deserialising into a map[string]interface{} but this didn't give the rich typing I wanted to return to the user. After a long time digging through the JSONAPI implementation and an associated PR to allow nested structs (google/jsonapi#99), I found that the go-fastly dependency was on an older version of the library before this PR came in. Updating the version, and playing around some more with the slice of structs instead of slice of pointers, finally got this to work. Furthermore I found that the Header in ListTLSSubscriptions needed to include the "Accept" header for "application/vnd.api+json" so I added it back in. I previously removed it as it didn't seem to do anything with the Filter and Pagination parameters, but it seems to be required for Include to work. * Avoid use pointers and uint in ListBulkCertificatesInput struct * Add allow_untrusted_root field to platform TLS * Get Certificate ID for TLS Subscriptions when available The certificate can't really be queried in the same way as the custom TLS certificate, but the ID will be present in the TLS activation automatically created for the TLS subscription, so can help filter for this activation. * Remove (TLS)\w* from field names of TLS structs * Add CommonName to Create, Get, and List TLSSubscription functions * Add "force" flag to TLS Subscription deletion Allows deleting a subscription with active domains; a potentially dangerous operation if the subscription handles production traffic. By using a flag, the user opts in to this risky behaviour and takes responsibility for knowing what they are doing. * go mod tidy * Add comments describing new structs and function * Document TLSAuthorizations struct Co-authored-by: Will May <will.j.may@gmail.com>
This mainly addresses #49 and has nothing todo with #21
The marshalling function is able to convert a nested struct into a valid json:api representation but you can't use the output for the unmarshalPayload function since it fails when a struct is used as an attribute.
This should be possible according to the json:api spec: http://jsonapi.org/format/#document-resource-object-attributes
The PR adds this functionality with the limitation of pointers. At the moment this only works with values (as shown in the added tests)
I also had to restructure the unmarshalNode function in order to convert the attributes recursively. This should simplify the implementation of #95