oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Add type for nested structs.

Open winder opened this issue 5 years ago • 10 comments

It is hard to initialize types with nested objects, because the nested objects don't have a type.

For example:

"TransactionResponse" : {
    "type" : "object",
    "description" : "Type with a nested object",
    "properties" : {
        "id" : {
            "type" : "string",
            "description" : "Transaction ID"
        },
        "params" : {
            "type" : "object",
            "description": "Params"
            "properties" : {
                "nested-param" : {
                    "type" : "integer",
                    "description": "Property of the nested object."
                }
            }
        }
    }
}

Generates code like:

type TransactionResponse struct {
    // Type with a nested object
    Id *string `json:"id,omitempty"`
    
    // Params
    Params *struct {

        // Property of the nested object.
        NestedParam *int  `json:"nested-param,omitempty"`
}

Now if I want to initialize the object I need something like:

nested := struct {
    NestedParam *int  `json:"nested-param,omitempty"`
} {
    NestedParam: 1234,
}

response := TransactionResponse {
    Id: "abcd",
    Params: &nested,
}

Ideally the nested anonymous struct would be given a type and generated closer to:

type NestedTransactionResponseParams struct {
        // Property of the nested object.
        NestedParam *int  `json:"nested-param,omitempty"`
}

type TransactionResponse struct {
    // Type with a nested object
    Id *string `json:"id,omitempty"`
    
    // Params
    Params *NestedTransactionResponseParams
}

Looking at the code, it seems like a reasonable way to implement this would be to have GenerateGoSchema return an array of TypeName objects rather than building up a tree of them (and then wrapping the tree in a TypeName. This should work recursively as well. The function is called in 12 places so I haven't spent the time to try and implement a solution. If I could get a thumbs up that such a solution makes sense I might give it a try, but until then I'll update my design to explicitly create types for each of the nested objects.

winder avatar Mar 02 '20 13:03 winder

I have code to generate nested structs, it's used for generating structs whenever we have an additionalProperties clause, so that I can override marshaling and unmarshaling code.

This particular one is a judgement call. I erred on the side of not generating these structs because if you define a type for them under components/schemas and then $ref to that type, my code will generate a Go type for you.

My assumption was that you control your swagger files, so that you can do that.

This would be a heavy change, but it's certainly possible, I'm just not sure if it's necessary given the type generation under components.

deepmap-marcinr avatar Mar 04 '20 00:03 deepmap-marcinr

In my case I was hoping to minimize the types defined under components/schemas because I'm also planning to use the spec for documentation and was trying to minimize the number of types listed there.

In one extreme cases for the project I'm working on now, pulling out the nested objects generated an extra 13 types.

winder avatar Mar 05 '20 18:03 winder

I agree, I would like to get this done, it's just a lot of work - this code is like a minefield, since the Swagger spec is so complex, where anytime I make a change like that, I suffer greatly :) I Was thinking that I could provide an annotation in the X-something fields to hint that you want a type for an inner struct, and provide a typename.

deepmap-marcinr avatar Mar 05 '20 20:03 deepmap-marcinr

I believe it. I spent a morning reviewing the code and there's definitely a lot going on in GenerateGoSchema. It seems like that function could be refactored to return an array of TypeName objects, but then depending on what calls GenerateGoSchema the results were processed in slightly different ways, I definitely saw a lot of opportunities for subtle regressions if I tried to make the change.

An annotation would work, but I think a simple convention of concatenating field names would be good. Thats what I did for my hand-made nested objects, and is also how openapi-generator seems to do it. I evaluated a all the generators I could find before picking oapi-codegen.

Basically a "Transaction" object like this:

{
    "Signature": {
        "MultiSignature": {
            "SubSignature": "<sig>"
        }
    }
}

Would generate Transaction, TransactionSignature, TransactionSignatureMultiSignature and TransactionSignatureMultiSignatureSubsignature.

winder avatar Mar 05 '20 20:03 winder

@winder If you are using github.com/getkin/kin-openapi you can put all your schemas in components/schemas to keep the json size down, and for displaying it (docs) you can resolve all the refs to make it easier to read using ResolveRefsIn(). This way oapi-codegen will still create the types for you, and you will still have a full json without $refs.

Jleagle avatar Mar 10 '20 11:03 Jleagle

For what it's worth I actually like the fact this library doesn't create named types for things unless you create a #/components/schemas/MyNamedThing.

IMO naming my schemas in my specs has lead to better documentation overall. I agree that maintaining a spec with lots of models can be a bit unwieldy, in my own projects I have my specs declared across files and combined before I generate code with oapi-codegen.

EG:

openapi.yaml 
spec/
  base.openapi.yaml
  common.components.yaml
  common.parameters.yaml
  common.schemas.yaml
  pets.partial.yaml
  store.partial.yaml
  user.partial.yaml
//go:generate yaml-merge-all-files -in=spec/ -out=openapi.yaml
//go:generate gobin -m -run github.com/deepmap/oapi-codegen/cmd/oapi-codegen --package=clientlib --generate types -o ./pkg/v1/types.gen.go ./openapi.yaml

Maybe that might help in your case in lieu of being able to automatically generate the code you want

pseudo-su avatar Mar 20 '20 23:03 pseudo-su

Transaction

I encountered the same problem, and I really hope oapi-codegen can support such a function

jesson1 avatar Mar 11 '21 02:03 jesson1

My assumption was that you control your swagger files, so that you can do that.

Sorry that this is a bit late, but this assumption is wrong in the majority of professional environments. You'll either be at the whims of an API team, or you'll be consuming these swagger files from an outside resource. For me, it's the latter. Without the ability to generate types, I'm having to deal with three levels of nesting, all pointers, and a lot of boilerplate that this tool was supposed to resolve.

DeadlySurgeon avatar Aug 20 '24 12:08 DeadlySurgeon

Would be glad to see this as well, at least as a param in the config... Sometimes my endpoints return a unique object and referencing everything seems like an overkill.

gren236 avatar Sep 28 '24 20:09 gren236

Is this project still maintained? I see that @deepmap-marcinr is willing to have it but just not have the time but https://github.com/oapi-codegen/oapi-codegen/pull/425 is opened a couple years ago. I can take a stab at it but would like to make sure if it'll go anywhere before committing to it.

muvaf avatar Sep 02 '25 07:09 muvaf