Proposal Details

There are several use-cases that are currently difficult:

  1. Writing dynamic functionality that interacts with both the Go struct type and the JSON type system. For example, one might want to know what JSON member names are supported by a particular Go struct type, or auto-generate an OpenAPI schema from the Go type, or write additional functionality that validates JSON according to other user-defined field tags. You could walk a Go struct and look for anything with a json struct tag, but the functionality is not trivial to write yourself.

  2. The ability to exclude (or modify) certain fields from a Go struct in JSON. You could declare a new Go type and copy the relevant fields over, but it's somewhat cumbersome, very prone to inconsistency bugs as the code evolves, and also not very performant as it requires a heap allocation every time.

Proposed API

Update (2025-07-31): Previously, the proposed API had Fields parameterized on T, but the revised API below no longer proposes that.

I propose a new package jsonstruct that satisfies these use cases:

package jsonstruct // "encoding/json/jsonstruct"

// Fields is an immutable list of JSON serializable fields in a Go struct type.
// The list excludes regular inline fields as those are auto-expanded into
// the nested fields that they include. The only field that is marked as
// `inline` or `unknown` is the "inlined fallback" field.
//
// A Fields value can be passed to [encoding/json/v2.WithFields]
// to specify that a particular struct type only consists of specific fields
// when marshaling or unmarshaling struct values of that particular type.
//
// The zero value of Fields is invalid and has no fields.
type Fields struct{ ... }

// FieldsOf returns a list of JSON serializable fields in a Go struct of type t
// and reports a multi-error for all structural errors detected in t.
// The list of fields is partial if an error is reported.
func FieldsOf(t reflect.Type) (Fields, error)

// FieldsFor returns a list of JSON serializable fields in a Go struct of type T
// and reports a multi-error for all structural errors detected in T.
// The list of fields is partial if an error is reported.
func FieldsFor[T any]() (Fields, error)

// Type is the type that this is a list of fields for.
func (Fields) Type() reflect.Type

// All returns an iterator over all JSON serializable fields
// in depth-first order of reachability from the root struct type.
// The last yielded field is the "inlined fallback" if present.
// It excludes fields previously excluded by [Fields.Exclude].
func (Fields) All() iter.Seq[Field]

// LookupIndex looks up a field by [Field.Index].
// It returns the zero value and false if not found.
func (Fields) LookupIndex(i int) (Field, bool)

// LookupJSONName looks up a field by [Field.JSONName].
// It returns the zero value and false if not found.
// The lookup is case-sensitive.
func (Fields) LookupJSONName(s string) (Field, bool)

// InlinedFallback returns the "inlined fallback" field if present.
// It returns the zero value and false if not present.
func (Fields) InlinedFallback() (Field, bool)

// Exclude excludes fields for which exclude reports true and returns a new list.
// The [Field.Index] of remaining fields do not change.
func (fs Fields) Exclude(exclude func(Field) bool) Fields

// Field is a JSON serializable struct field.
type Field struct {
    // Index is a unique index number for a particular struct type.
    // The number does not change when fields are excluded.
    Index int

    // GoField is type information about the Go struct field.
    // The [reflect.StructField.Index] field must not be mutated. 
    GoField reflect.StructField

    // JSONName is the JSON object member name for this field.
    // If no name is explicitly specified in a `json` tag,
    // it uses the Go field name.
    JSONName string

    CaseStrict bool   // whether `case:strict` is specified
    CaseIgnore bool   // whether `case:ignore` is specified
    Inline     bool   // whether `inline` is specified
    Unknown    bool   // whether `unknown` is specified
    OmitZero   bool   // whether `omitzero` is specified
    OmitEmpty  bool   // whether `omitempty` is specified
    String     bool   // whether `string` is specified
    Format     string // the `format` string value
}

and in the "json" package we support a new option:

package json // "encoding/json/v2"

// WithFields specifies that structs of type T are to be serialized
// as if only the provided fields exist in struct type T.
// The type associated with fields must be of type T.
func WithFields[T any](fields jsonstruct.Fields) Options

The proposed API only supports excluding certain fields, but could be extended in the future to support other operations such as dynamically changing the JSON tags, reordering the fields, etc.

Example usage:

Suppose you have an HTTP API where the request can specify particular fields that it wants returned. This is difficult to implement today, but is relatively easy to implement with the API:

func (db *DB) handleListDevices(w http.ResponseWriter, r *http.Request) {
    deviceFields, err := jsonstruct.FieldsFor[Device]()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Parse the "fields" query parameter for requested fields. 
    requestedFields := make(map[string]bool)
    for name := range strings.SplitSeq(r.URL.Query().Get("fields"), ",") {
        if _, ok := deviceFields.LookupJSONName(name); !ok {
            http.Error(w, fmt.Sprintf("unknown name %q", name), http.StatusBadRequest)
            return
        }
        requestedFields[name] = true
    }
    filteredFields := deviceFields.Exclude(func(f jsonstruct.Field) bool {
        return !requestedFields[f.JSONName]
    })

    // As the database reads the list of devices,
    // it can ignore computing expensive fields that
    // the client did not request data for.
    devices := db.loadDevices(filteredFields)

    // Serialize the list of devices and only output the requested fields. 
    if err := json.MarshalWrite(w, devices, json.WithFields(filteredFields)); err  != nil {
        log.Printf("json.MarshalWrite error: %v", err)
    }
}

In #28143, there's a proposal for the ability to specify certain fields as being "read-only". Such a feature could be implemented using this feature:

type User struct {
    ID        int       `json:"id" readonly:""`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedBy int       `json:"createdBy" readonly:""`
    CreatedAt time.Time `json:"createdAt" readonly:""`
}

// writeableUserFields is a list of only the writeable fields in User.
// Specifically, it excludes all fields with the `readonly` tag.
var writableUserFields = func() jsonstruct.Fields {
    fields, err := jsonstruct.FieldsFor[User]()
    if err != nil {
        panic(err)
    }
    return fields.Exclude(func(f jsonstruct.Field) bool {
        _, readonly := f.GoField.Tag.Lookup("readonly")
        return readonly
    })
}()

// UnmarshalJSONFrom configures User to unmarshal only into writeable fields.
// It reports an error when attempting to unmarshal into any other field.
func (u *User) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
    type UserNoMethods User // avoid infinite recursion with unmarshal
    return json.UnmarshalDecode(dec, (*UserNoMethods)(u),
        json.WithFields(writeableUserFields), // specify that only "name" and "email" fields exist in User struct
        json.RejectUnknownMembers(true))      // specify that unknown fields are treated as an error (e.g., "created")
}

Implementation

The "jsonstruct" package is exposing the internal implementation of "encoding/json/v2" with a cleaner and stable API. The features exposed in the API are functionality already documented as being supported in "json/v2", so exposing it does not bind "json/v2" down to a particular implementation approach or a particular set of functionality.

@merovius proposed typed struct tags (#74472) and this proposal can interoperate with that proposal as it neither depends on it, nor would the API change as a result of it. The jsonstruct.Field.GoField is of the reflect.StructField type. Thus, should #74472 be adopted, its functionality will implicitly be made available through the reflect.StructField type that the API exposes. In fact, user access to custom tags would be more efficient with #74472 since the user no longer needs to rely on string parsing.

The API documents that the nested reflect.StructField.Index field must not be mutated by callers. Note that the "reflect" package already uses unsafe tricks to make attempted mutations of this field be a runtime panic. This API is simply surfacing that reality.

Comment From: arp242

I wonder if it makes sense to name this something more generic than encoding/json/jsonstruct, so it can also be useful for other encoders such as XML, TOML, YAML, etc? Other than some naming, nothing here really stands out as JSON-specific, I think?

Comment From: dsnet

Actually, it is fairly JSON-specific as it only reports JSON-serializable fields.

The degree to which this shares similarity with XML and other formats is that it is often the case (but not always) that the set of serializable fields in JSON is the same as those in XML. Also, the set of tag features supported by "json" is similar (but not exactly equal to) those supported by XML and other formats.

Also, it is more readable to see jsonstruct.Fields[MyType] and know that it's a list of fields relevant to JSON serialization. Similarly, xmlstruct.Fields[MyType] is clear that it's only a list of fields relevant to XML serialization.

If there is any attempt at sharing code due to similarities, it would probably be an internal package that backs the implementation of both "jsonstruct" and "xmlstruct".

Comment From: rittneje

Suppose you have an HTTP API where the request can specify particular fields that it wants returned. This is difficult to implement today, but is relatively easy to implement with the API

The proposed code does not seem to account for nested fields. How would that work? You could split on dots, but then it seems like you'd need a way to get from the reflect.Type of each reflect.StructField to its jsonstruct.Fields. And then I'm not sure what you'd be passing the database layer in that case, as it could not just be the top-level jsonstruct.Fields.

This also alludes to a potential deficiency in the proposed json.WithFields. Suppose I have a set of structs like so:

type Foo struct {
    A Bar
    B Bar
}

type Bar struct {
    ID int
    Name string
}

And let's suppose my filter is "A.ID,B.Name". Because both A and B have the same struct type (Bar), I cannot use json.WithFields as it applies to all fields of the given type.

(Personally, I also don't see why jsonstruct.Exclude is necessary, given you can just use omitempty or omitzero for the same purpose.)

Comment From: apparentlymart

From the description I'm not sure what is the purpose of the generic type parameter T on Fields, since nothing in its API makes use of T except the Exclude method, and that seems to include T only because it's returning a value of the same type it was called on.

Do you imagine there being an unexported field of Fields that would involve type T, or is that just so that the zero value of the type is immediately ready to use?

I don't mean to say that doing that is necessarily wrong if so, but it seems quite unconventional. I would've expected only the FieldsFor function to be generic, similar to how reflect.TypeFor is generic but reflect.Type is not, so that it's possible to write functions that can work with any Fields without those functions also needing to be generic.

Comment From: nikpivkin

@apparentlymart Apparently, the presence of the parameter in Fields allows you to work with structure tags without calling FieldsFor and validating them:

var deviceFields jsonstruct.Fields[Device]
deviceFields.LookupJSONName(name)

It seems to me that it is possible to provide a parameterized function that initializes Fields without validating tags.

Comment From: Merovius

The zero value of Fields represents all serializable fields in T.

I find that an awkward choice. I understand Fields to be a set of fields. I would expect the zero value of a set-type to be the empty set, which is then grown by adding to it. Making the zero value the universe and building the concrete set by removing from it seems extremely unconventional to me.

It is also what forces the "only request certain fields" path to be inefficient, as it requires first building a set of included fields and then exclude everything else.

Another kind of dubious aspect of that is that the FieldsFor function returns an error. So, it seems clear that the zero-value of Fields[T] isn't all that correct, because it ignores the potential errors. At the same time, whether FieldsFor returns an error is a static property of the type. So it isn't actually something that I would use in production code, because any error it returns is a bug in the program, not a user-mistake (so I would panic on any error). So really, it seems to me that in practice, I would only call FieldsFor in a test and there would only check the error. And then in production code, would always write var f jsonstruct.Fields[T], as that gives me the same information.

So, personally, I would design this the other way around:

// Fields is an immutable list of JSON serializable fields in a Go struct of type T.
// The zero value of Fields represents no fields.
// A non-struct kind has no serializable fields.
type Fields[T any] struct{ ... }

// FieldsFor returns a list of JSON all serializable fields in a Go struct of type T
// and reports a multi-error for all structural errors detected in T.
// The list of fields is partial if an error is reported.
func FieldsFor[T any]() (Fields[T], error)

// Exclude excludes fields for which exclude reports true and returns a new list.
// The [Field.Index] of remaining fields do not change.
func (fs Fields[T]) Exclude(exclude func(Field) bool) Fields[T]

// Include includes fields for which include reports true and returns a new list.
// The [Field.Index] of remaining fields do not change.
func (fs Fields[T]) Include(include func(Field) bool) Fields[T]

With this, it is (almost) equally convenient to build an inclusion list (e.g. the filter example) and an exclusion list (e.g. the readonly example): you either start with the zero value and call Include repeatedly, or you start with FieldsFor and call Exclude repeatedly.

It also makes sense that the zero value doesn't involve potential structural errors, because an empty set always has meaning. And now the actual return value of FieldsFor is also useful, not just the error.

Lastly, it also gives a clear meaning to the type parameter, as it is required for Include to work, if you start with the zero value.


The last yielded field is the "inlined fallback" if present.

Why? Does that mean the inlined fallback field is not yielded in the regular order (i.e. it is moved to the back), or is it yielded twice? I assume the former, but that drops information (no idea whether it's important information, but it could be). Also, a range loop does not know whether or not the current field is the last yielded one. There is also no way to tell if a given field is an inlined fallback, or a regular inlined field (except by inspecting its type). So I don't see how user code can even act on this information.

Unless there is a really good reason why this choice is practical, I'd instead add e.g. func (Field) IsFallback() bool and then yield the inlined fallback in regular iteration order. The user can then skip it with continue and use InlinedFallback after the loop, to get the same semantic as with the proposal, but the information of where the inlined fallback field appears is preserved.

Also, the iter naming conventions would suggest naming the method All, not Fields. Fields is a collection of Field and Fields.Fields stutters anyways.


Two additions I would find interesting:

// WithFieldOptions applies the given options when marshalling or unmarshalling the given fields.
func WithFieldOptions[T any](jsonstruct.Fields[T], Options) Options

// GetField returns the field marshaled from or unmarshalled into at the current parsing depth.
func GetField(Options) jsonstruct.Field

The first is to address similar concerns as @rittneje mentions, where you have two fields of the same type, that you want to treat differently.

I think the second is what you alluded to in your comment on #71664, about exposing Field as a read-only option? In any case, it seems required for this API to really be useful to make UnmarshalerFrom/MarshalerTo behavior field-specific (in particular, for .Format to be useful).

Comment From: ianlancetaylor

As far as I can see the original proposal might as well be

type Fields[T] struct{}

The Fields method will look at the type argument and return the fields. The Exclude method will do the same. To say that the zero value represents all fields in the type is a conceit that doesn't necessarily have anything to do with the implementation.

Of course, if that is true, then I don't see what we gain from the Fields[T] type at all. Is it just going to cache some information? Why not

// Fields returns an iterator over the JSON serializable fields of T.
// It panics if T is not a struct type or if the fields have any errors.
func Fields[T any]() iter.Seq[Field]

We could add a filter argument to produce Exclude, but the loop over the iterator can just skip those fields anyhow.

The original proposal has things like an Index field and LookupIndex and LookupJSONName methods but it's not clear why they are essential. The loop in the handleListDevices could run the other way--get the field names from the URL and then loop over the fields in Device.

Comment From: dsnet

Thank you for all your thoughts. I revised the API in my original post to not make Field parameterized.

The main reason I originally had it be generic was for two reasons: 1. There was an ergonomic benefit to having the zero value of Field[T] be the full list of available fields. 2. There was a type-safety benefit where json.WithFields could not possibly be given a jsonstruct.Fields argument with a type mismatch. Note that json.WithFields continues to be parameterized on a T.

But I believe a parameterized Fields type is more problematic than helpful.

The proposed code does not seem to account for nested fields. How would that work? You could split on dots, but then it seems like you'd need a way to get from the reflect.Type of each reflect.StructField to its jsonstruct.Fields.

By removing the type parameter from Fields, we can at least allow users to recursively lookup a jsonstruct.Fields from a reflect.Type and implement some degree of nested filtering themselves.

There is also no way to tell if a given field is an inlined fallback, or a regular inlined field (except by inspecting its type). So I don't see how user code can even act on this information.

An inlined fallback would be identifiable by having jsonstruct.Field.Inline or jsonstruct.Field.Unknown be true. There is only ever one possible inline fallback since it represents the superset of all JSON members that doesn't statically correspond with a particular field. An regular inlined field will not appear in the list since it is automatically expanded (as it does not correspond with a particular JSON object member).

The first is to address similar concerns as @rittneje mentions, where you have two fields of the same type, that you want to treat differently.

I've been thinking a bit about general-purpose filtering (as motivated by your https://github.com/golang/go/issues/71664#issuecomment-2988780612), where you can express something like "this option is only applicable within some depth, under (or above) some JSON pointer, etc.", but I haven't yet come up with a concrete API for that. I think we can discuss further in #71664 though this proposal could certainly benefit from such a concept.

Of course, if that is true, then I don't see what we gain from the Fields[T] type at all. Is it just going to cache some information?

A major utility of an explicit Fields type is so that we can pass it as an options argument using json.WithFields to specify that a particular struct type T only supports some subset of fields.

(Personally, I also don't see why jsonstruct.Exclude is necessary, given you can just use omitempty or omitzero for the same purpose.)

This doesn't work well if you have an API contract where you need to actually serialize the zero (or empty) value. I guess you could wrap every single field in a wrapper type, but that's a bit cumbersome.

It also doesn't work well if you do not have write access to a particular Go struct. In a piece of code I was looking at optimizing, it was effectively making a local copy of a struct value, just to serialize specific fields, and led to a significant amount of allocations. This is functionally similar to copying the entire original struct and zeroing out the fields you don't care about. This is also a lot of CPU spent zeroing the fields, and then a lot of CPU within the "json" package to check if the fields are zeroed. The Exclude feature allows the "json" package to ignore the existence of such fields entirely without needing to do any copying, re-declaring types, manually zeroing fields (which is prone to break when new fields are added), etc.

The original proposal has things like an Index field and LookupIndex and LookupJSONName methods but it's not clear why they are essential.

LookupIndex is not essential, but it did permit for a nice optimization where you can lookup existence in a set using a bitmap rather than a Go string lookup in a map[string]struct{}.

You can imagine something like (I'm ignoring errors):

// Global declared variable 
var (
    deviceFields = jsonstruct.FieldsFor[Device]()

    deviceAddressesField = deviceFields.LookupJSONName("addresses").Index
    deviceConnectivityField = deviceFields.LookupJSONName("connectivity").Index
)

// loadDevice loads a device from the database for the specified id.
// It avoids populating certain fields that are not included in fields.
func (db *DB) loadDevice(id deviceID, fields jsonstruct.Fields) (*Device, error) {
    d := &Device{...}
    if _, ok := fields.LookupIndex(deviceAddressesField); ok {
        d.addresses = db.computeDeviceAddresses(...) // assume this is expensive
    }
    if _, ok := fields.LookupIndex(deviceConnectivityField ); ok {
        d.connectivity = db.computeDeviceConnectivity(...) // assume this is expensive
    }
    return d, nil
}

Comment From: rittneje

This doesn't work well if you have an API contract where you need to actually serialize the zero (or empty) value. I guess you could wrap every single field in a wrapper type, but that's a bit cumbersome.

If the unmarshaler is also written in Go, then I feel this proposal still doesn't address the underlying issue there. The client needs presence information for each field (to distinguish zero/empty from omitted), and I don't think this style of API would help there. In particular, this kind of situation is prevalent for any server that handles JSON merge patch.

In other words, there is value to making that kind of wrapping easier, even if this proposal chooses not to address such use cases.

It also doesn't work well if you do not have write access to a particular Go struct.

In this case, I would imagine it is more generally useful to be able to control everything about the marshaling, such as the field names, not just what is omitted/excluded. To that end, would something more generic be more advantageous? For example, a callback that says "I'm marshaling this field of this struct, what should I do?"

Comment From: adeinega

@dsnet, I love and support this proposal.

Thank you for doing that!

One of the use cases I have in mind and would like to share is that there would be a clean way to mark certain fields in a Go struct as "selectively disclosed" and build a marshaller that honors them

This is used in privacy-preserving technologies such as IETF Selective Disclosure for JWTs (SD-JWT) and SD-JWT-based Verifiable Credentials (SD-JWT VC).

for example ``go type Credential struct { FirstName stringjson:"first_name"GivenName stringjson:"given_name"IsOver21 booljson:"is_over_21" sd:"true"` }


would be marshaled into something like

{ "_sd": [ "09vKrJMOlyTWM0sjpu_pdOBVBQ2M1y3KhpH515nXkpY" ], "first_name": "Andrii", "given_name": "Deinega", "_sd_alg": "sha-256" }



**Comment From: Merovius**

@dsnet The docs no longer mention what a zero `Fields` means. I would still (if the implementation allows) vote in favor of having it mean "an empty list of fields" and to add an `Include` method, to avoid having to allocate and build `requestedFields` in the `handleListDevices` example.

**Comment From: dsnet**

I'm not sure how to make the zero `Fields` valid since we no longer know what Go struct type it is associated with.

My original design avoided an additive API to avoid answer questions on how to validate that a particular `Field` value that the caller is inserting is valid. We can support insertions in the future, but it didn't seem essential for the initial API.

> to avoid having to allocate and build `requestedFields` in the `handleListDevices` example.

My example didn't show the optimization, but this is the reason why `Field` has an `Index` field since you can use it with a bitset. Given that the vast majority of Go structs have less than 64 fields, [you can use a bitset to represent the relevant fields](https://pkg.go.dev/tailscale.com/util/set#IntSet). For example:
```go
func (db *DB) handleListDevices(w http.ResponseWriter, r *http.Request) {
    deviceFields, err := jsonstruct.FieldsFor[Device]()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Parse the "fields" query parameter for requested fields. 
    var requestedFields sets.IntSet[int]
    for name := range strings.SplitSeq(r.URL.Query().Get("fields"), ",") {
        if f, ok := deviceFields.LookupJSONName(name); !ok {
            http.Error(w, fmt.Sprintf("unknown name %q", name), http.StatusBadRequest)
            return
        } else {
            requestedFields.Add(f.Index)
        }
    }
    filteredFields := deviceFields.Exclude(func(f jsonstruct.Field) bool {
        return !requestedFields.Contains(f.Index)
    })

    // As the database reads the list of devices,
    // it can ignore computing expensive fields that
    // the client did not request data for.
    devices := db.loadDevices(filteredFields)

    // Serialize the list of devices and only output the requested fields. 
    if err := json.MarshalWrite(w, devices, json.WithFields(filteredFields)); err  != nil {
        log.Printf("json.MarshalWrite error: %v", err)
    }
}

The sets.IntSet type is designed that it never allocates for small sets of integers near zero.

Also, the implementation of Fields.Exclude would simply populate an internal bitmask of fields to exclude. It doesn't need to copy the entire list of fields.

Comment From: dsnet

@rittneje, I don't quite understand the use case you're getting at. Could you provide a concrete example? Thanks.

In this case, I would imagine it is more generally useful to be able to control everything about the marshaling, such as the field names, not just what is omitted/excluded. To that end, would something more generic be more advantageous? For example, a callback that says "I'm marshaling this field of this struct, what should I do?"

Some of the functionality that you just mentioned could be future extensions to this API, but the initial proposal is deliberately constrained in what it can do. As we expand the functionality of this API, it starts getting into territory that other options in "json/v2" package can already handle. For example, json.WithMarshalers and json.WithUnmarshalers already provides users a way to customize how to serialize a particular type.

Comment From: dsnet

@adeinega I'm glad this could potentially solve your use case. In your example, where does the _sd and _sd_alg fields come from?

Comment From: rittneje

I don't quite understand the use case you're getting at. Could you provide a concrete example? Thanks.

Let's say I have an API that returns a resource like so:

{
    "A": "foo",
    "B": 123
}

In Go, I would model this as a struct like so:

type MyResource struct {
    A string
    B int
}

However, now I want to support JSON merge patch. As per the spec, I need to be able to distinguish an explicit null from omitted. Historically, this has forced us to make a second struct (shared by server and client) with double pointers and a custom UnmarshalJSON method, which is rather cumbersome.

type MyResourcePatch struct {
    // If the outer pointer is nil, the field was not present.
    // If the inner pointer is nil, the field was explicitly null.
    A **string `json:",omitempty"`
    B **int `json:",omitempty"`
}

func (p *MyResourcePatch) UnmarshalJSON(b []byte) error {
    var tmp struct {
        A json.RawMessage
        B json.RawMessage
    }

    if err := json.Unmarshal(b, &tmp); err != nil {
        return err
    }

    if tmp.A != nil {
        p.A = new(string)
        json.Unmarshal(tmp.A, p.A)
    }

    if tmp.B != nil {
        p.B = new(int)
        json.Unmarshal(tmp.B, p.B)
    }

    return nil
}

There is some overlap with this proposal, in that the client generating the patch also needs to be able to exclude certain fields. However, that does not help on the server side.

For example, json.WithMarshalers and json.WithUnmarshalers already provides users a way to customize how to serialize a particular type.

I saw those, but it didn't seem like there was a straightforward way to use them. That is, you can say you want to customize the marshaling of a struct type, but I don't see any tools to actually implement that without just doing it all manually in a case like this.

Comment From: adeinega

@dsnet, it's a part of Selective Disclosure JWT (SD JWT), array _sd includes the digests of disclosures. In my example, it could be something like

["cn0iTq0oVLLFIDsfv2zZbW", "is_over_21", true]

for field is_over_21 of the Credential type from my example.

Section Introduction from https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/ and the whole spec explains what this is and how it's used much better than I could in one or two GitHub comments.

SD-JWT is based on an approach called "salted hashes": For any data element that should be selectively disclosable, the Issuer of the SD- JWT does not include the cleartext of the data in the JSON payload of the JWS structure; instead, a digest of the data takes its place.

For presentation to a Verifier, the Holder sends the signed payload along with the cleartext of those claims it wants to disclose. The Verifier can then compute the digest of the cleartext data and confirm it is included in the signed payload. To ensure that Verifiers cannot guess cleartext values of non-disclosed data elements, an additional salt value is used when creating the digest and sent along with the cleartext when disclosing it.