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

Using generated union type as response returning empty json

Open idleway opened this issue 2 years ago • 10 comments

I have request that returning in response type Event defined like:

    event:
      oneOf:
        - $ref: '#/components/schemas/eventTypeOnetimeEditableField'
        - $ref: '#/components/schemas/eventTypeRepeatableEditableField'

Generated types are:

type Event struct {
	union json.RawMessage
}
func (t Event) MarshalJSON() ([]byte, error) {
	b, err := t.union.MarshalJSON()
	return b, err
}

type PostDescribeEventById200JSONResponse Event

The problem is PostDescribeEventById200JSONResponse on json marshalling returning empty json "{}". This happens because PostDescribeEventById200JSONResponse is not using MarshalJSON() of underlying struct Event.

Using type alias instead of underlying type solving my problem:

type PostDescribeEventById200JSONResponse = Event

idleway avatar Feb 19 '23 17:02 idleway

I can confirm that this is an issue indeed.

I fixed it by adding MarshalJSON method to (using your example) PostDescribeEventById200JSONResponse:

func (c PostDescribeEventById200JSONResponse) MarshalJSON() ([]byte, error) {
	return Event(c).MarshalJSON()
}

miro-ko avatar Mar 14 '23 09:03 miro-ko

did you solve this with a template @idleway ? @miro-ko did you add this manually to the generated file or do you have a nice way ? or is there any lead how to fix this issue long term ?

I would create a PR if I had a starting point for a solution

gerhardwagner avatar Jun 12 '23 13:06 gerhardwagner

I have similar issue - in my case the field is just one of the fields that are contained in a containing response object. The generated code for the strict server interface does not marshall that imbedded union as part of the code that gets generated for the 201 response creation.

srjinatl avatar Jun 12 '23 23:06 srjinatl

I spend some time digging around and this is what I found:

in the strict-interface.tmpl https://github.com/deepmap/oapi-codegen/blob/ad62d73d8d22aa251d06b490b259790919899eab/pkg/codegen/templates/strict/strict-interface.tmpl#L44 we can see that the = for aliasing is bound to IsRef which is a function on the Schema https://github.com/deepmap/oapi-codegen/blob/ad62d73d8d22aa251d06b490b259790919899eab/pkg/codegen/schema.go#L42-L44

digging deeper and following my response object I also found this in GenerateGoSchema: https://github.com/deepmap/oapi-codegen/blob/ad62d73d8d22aa251d06b490b259790919899eab/pkg/codegen/schema.go#L233-L246 here is the place where the RefType could have been set but it is not but instead the GoType is populated with a var called refType I thought this could be a small typo and just changed it to

return Schema{
	GoType:         refType,
	RefType:        refType,
	Description:    schema.Description,
	DefineViaAlias: true,
	OAPISchema:     schema,
}, nil

now this generates me aliased types and therefor the unions MarshalJSON function is used on the Visit func and the JSON body is correctly there

I am just not sure if this could break any cases I did not consider

since I need this I will fix this on my fork but I can also make a PR to fix this here

gerhardwagner avatar Jun 13 '23 09:06 gerhardwagner

Is there any progress on this issue? I have the similar problem

illiafox avatar Jul 16 '23 12:07 illiafox

I think this is the same issue as discussed here: https://github.com/deepmap/oapi-codegen/issues/1122#issuecomment-1726614770

It seems to be a problem when you have a content of response of type schema oneOf:

Foo:
  schema:
     oneOf:
        - ref$: 'obj1'
        - ref$: 'obj2'

Generates responses as:

type Foo200JSONResponse FooResponse

type FooResponse {
   union json.RawMessage
}

which decodes as {} when:

func (response  Foo200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
  w.Header().Set("Content-Type", "application/json")
  w.WriteHeader(200)
  
  return json.NewEncoder(w).Encode(response)
}

is called.

It would work if we would encode response.union and not response itself, as the encoding the response returns {} and not the content of the union.

Proposal that if the response has oneOf schema, then encode the response.union and not response as the 200 object is a type and not a struct.

func (response  Foo200JSONResponse) VisitAcceptTicketResponse(w http.ResponseWriter) error {
  w.Header().Set("Content-Type", "application/json")
  w.WriteHeader(200)
  
  return json.NewEncoder(w).Encode(response.union)
}

marmiha avatar Sep 19 '23 22:09 marmiha

My PR #1231 has been working for me for a while which uses the fix mentioned above, however I have just found one small issue. I'm not even sure it's a real issue, but more a bit "meh".

If you have a schema in your specification which is just an alias for another type, i.e.

components:
  schemas:
    Path:
      type: string

You end up with a type declaration:

type Path = string

The reason I'm wrapping this up in a defined type is I want extra methods on the Path type to provide certain logic, but now it's declared as a go alias this isn't possible.

It's not the end of the world, but if we could figure out how to differentiate these types of aliases versus union types that would be good.

dan-j avatar May 04 '24 10:05 dan-j

Any progress?

MateusFrFreitas avatar May 06 '24 20:05 MateusFrFreitas

It seems that there aren't any problems in the code generation of union type model.

The generation of strict response was handled independently in strict-interface.tmpl. I found that the .Schema.UnionElements of the strict response is empty during the generation (because it's a Defined Type of a union type, not the union type itself ?)

{{ ..Schema.TypeDecl }} may be the extractly same definition as the union type, but without calling the union.tmpl, the MarshalJSON wasn't generated.

I think we can fix this problem if we can

  1. identify if a response could be a union type or not in strict-interface.tmpl (the only way I found is checking the OpenAPI schema through .Schema.OAPISchema)
  2. or treat the strict response object as a trivial model (like the other union types, and generate the MarshalJSON for it)

I'm currently using the method 1. Patching the oapi-codegen through user-template works for me. https://github.com/oapi-codegen/oapi-codegen/issues/1665#issuecomment-2179764402

akishichinibu avatar Jun 24 '24 01:06 akishichinibu

We have exactly the same problem. Support for oneOf would be highly appreciated 🙏 as we now have well documented APIs without (fully working) automatic server/client generation.

bkleef avatar Aug 30 '24 08:08 bkleef