Skip to content

Fix nil pointer dereference in ReflectFromType with ExpandedStruct (fix #163)#186

Merged
samlown merged 3 commits intoinvopop:mainfrom
edznux-dd:fix/expanded-struct-nil-deref
Apr 23, 2026
Merged

Fix nil pointer dereference in ReflectFromType with ExpandedStruct (fix #163)#186
samlown merged 3 commits intoinvopop:mainfrom
edznux-dd:fix/expanded-struct-nil-deref

Conversation

@edznux-dd
Copy link
Copy Markdown
Contributor

This PR fixes #163

I'm not 100% sure if this is still maintained, but I stumbled uppon what I believe is a bug or at least an unexpected behavior.
The impact for my app was fairly low (dev time), but unexpected.

Example reproducer based on your documentation example:

package main

import (
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/invopop/jsonschema"
)

type SampleUser struct {
	ID int `json:"id"`
}

func main() {
	r := &jsonschema.Reflector{ExpandedStruct: true}

	// The following line works fine (based on the example from the doc)
	// s := r.ReflectFromType(reflect.TypeOf(SampleUser{}))

	// The following line panic (only if ExpandedStruct: true)
	s := r.ReflectFromType(reflect.TypeOf(map[string]interface{}{}))

	data, err := json.MarshalIndent(s, "", "  ")
	if err != nil {
		panic(err.Error())
	}
	fmt.Println(string(data))
}

The fix is "just" checking that the key we are deleting exists.

I've added a fuzz test in addition of the regression test, in case we want to discover more bugs in the future (and possibly let agent fix them for you!). The fuzz-test, tests a few more flags combination to have more code coverage.

When ExpandedStruct=true is combined with a non-struct reflect.Type
(slice, map, interface, or enum-tagged field), definitions[name] is nil
because only struct types register themselves via reflectStruct. The
unconditional dereference at reflect.go:191 panics.

Fall back to the base schema returned by reflectTypeToSchemaWithID when
no definition was registered. Adds a regression test covering the
affected type kinds and a fuzz test exercising ReflectFromType against
a pool of types and Reflector flag combinations.
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.34%. Comparing base (d39f13c) to head (b43264d).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #186      +/-   ##
==========================================
+ Coverage   89.30%   89.34%   +0.04%     
==========================================
  Files           4        4              
  Lines         729      732       +3     
==========================================
+ Hits          651      654       +3     
  Misses         59       59              
  Partials       19       19              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@samlown samlown left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! A few linter issues that I'll have a look at.

@samlown
Copy link
Copy Markdown
Contributor

samlown commented Apr 23, 2026

I'm not 100% sure if this is still maintained

We try, and actively use this in production, its just that time is very limited. Well tested PRs like this are great.

samlown and others added 2 commits April 23, 2026 14:43
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@samlown samlown merged commit 0d5bd75 into invopop:main Apr 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExpandedStruct panics with primitive types

3 participants