Fix Grype CVEs: update logrus and prometheus/prometheus

- Update github.com/sirupsen/logrus v1.9.0 -> v1.9.3 in test/go.mod
  to fix GHSA-4f99-4q7p-p3gh (High)
- Update github.com/prometheus/prometheus v0.35.0 -> v0.311.3
  to fix GHSA-vffh-x6r8-xx99 (Medium)
- Run go mod tidy and go mod vendor to update vendor directory
This commit is contained in:
Bruno Chauvet
2026-05-04 10:45:24 +03:00
committed by Ciprian Hacman
parent 255c6e602c
commit 97bb2fbb44
279 changed files with 17945 additions and 29165 deletions
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+109
View File
@@ -0,0 +1,109 @@
# jsonutils
`jsonutils` exposes a few tools to work with JSON:
- a fast, simple `Concat` to concatenate (not merge) JSON objects and arrays
- `FromDynamicJSON` to convert a data structure into a "dynamic JSON" data structure
- `ReadJSON` and `WriteJSON` behave like `json.Unmarshal` and `json.Marshal`,
with the ability to use another underlying serialization library through an `Adapter`
configured at runtime
- a `JSONMapSlice` structure that may be used to store JSON objects with the order of keys maintained
## Dynamic JSON
We call "dynamic JSON" the go data structure that results from unmarshaling JSON like this:
```go
var value any
jsonBytes := `{"a": 1, ... }`
_ = json.Unmarshal(jsonBytes, &value)
```
In this configuration, the standard library mappings are as follows:
| JSON | go |
|-----------|------------------|
| `number` | `float64` |
| `string` | `string` |
| `boolean` | `bool` |
| `null` | `nil` |
| `object` | `map[string]any` |
| `array` | `[]any` |
## Map slices
When using `JSONMapSlice`, the ordering of keys is ensured by replacing
mappings to `map[string]any` by a `JSONMapSlice` which is an (ordered)
slice of `JSONMapItem`s.
Notice that a similar feature is available for YAML (see [`yamlutils`](../yamlutils)),
with a `YAMLMapSlice` type based on the `JSONMapSlice`.
`JSONMapSlice` is similar to an ordered map, but the keys are not retrieved
in constant time.
Another difference with the the above standard mappings is that numbers don't always map
to a `float64`: if the value is a JSON integer, it unmarshals to `int64`.
See also [some examples](https://pkg.go.dev/github.com/go-openapi/swag/jsonutils#pkg-examples)
## Adapters
`ReadJSON`, `WriteJSON` and `FromDynamicJSON` (which is a combination of the latter two)
are wrappers on top of `json.Unmarshal` and `json.Marshal`.
By default, the adapter merely wraps the standard library.
The adapter may be used to register other JSON serialization libraries,
possibly several ones at the same time.
If the value passed is identified as an "ordered map" (i.e. implements `ifaces.Ordered`
or `ifaces.SetOrdered`, the adapter favors the "ordered" JSON behavior and tries to
find a registered implementation that support ordered keys in objects.
Our standard library implementation supports this.
As of `v0.25.0`, we support through such an adapter the popular `mailru/easyjson`
library, which kicks in when the passed values support the `easyjson.Unmarshaler`
or `easyjson.Marshaler` interfaces.
In the future, we plan to add more similar libraries that compete on the go JSON
serializers scene.
## Registering an adapter
In package `github.com/go-openapi/swag/easyjson/adapters`, several adapters are available.
Each adapter is an independent go module. Hence you'll pick its dependencies only if you import it.
At this moment we provide:
- `stdlib`: JSON adapter based on the standard library
- `easyjson`: JSON adapter based on the `github.com/mailru/easyjson`
The adapters provide the basic `Marshal` and `Unmarshal` capabilities, plus an implementation
of the `MapSlice` pattern.
You may also build your own adapter based on your specific use-case. An adapter is not required to implement
all capabilities.
Every adapter comes with a `Register` function, possibly with some options, to register the adapter
to a global registry.
For example, to enable `easyjson` to be used in `ReadJSON` and `WriteJSON`, you would write something like:
```go
import (
"github.com/go-openapi/swag/jsonutils/adapters"
easyjson "github.com/go-openapi/swag/jsonutils/adapters/easyjson/json"
)
func init() {
easyjson.Register(adapters.Registry)
}
```
You may register several adapters. In this case, capability matching is evaluated from the last registered
adapters (LIFO).
## [Benchmarks](./adapters/testintegration/benchmarks/README.md)
+8
View File
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Package adapters exposes a registry of adapters to multiple
// JSON serialization libraries.
//
// All interfaces are defined in package [ifaces.Adapter].
package adapters
+5
View File
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Package ifaces exposes all interfaces to work with adapters.
package ifaces
+84
View File
@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package ifaces
import (
_ "encoding/json" // for documentation purpose
"iter"
)
// Ordered knows how to iterate over the (key,value) pairs of a JSON object.
type Ordered interface {
OrderedItems() iter.Seq2[string, any]
}
// SetOrdered knows how to append or update the keys of a JSON object,
// given an iterator over (key,value) pairs.
//
// If the provided iterator is nil then the receiver should be set to nil.
type SetOrdered interface {
SetOrderedItems(iter.Seq2[string, any])
}
// OrderedMap represent a JSON object (i.e. like a map[string,any]),
// and knows how to serialize and deserialize JSON with the order of keys maintained.
type OrderedMap interface {
Ordered
SetOrdered
OrderedMarshalJSON() ([]byte, error)
OrderedUnmarshalJSON([]byte) error
}
// MarshalAdapter behaves likes the standard library [json.Marshal].
type MarshalAdapter interface {
Poolable
Marshal(any) ([]byte, error)
}
// OrderedMarshalAdapter behaves likes the standard library [json.Marshal], preserving the order of keys in objects.
type OrderedMarshalAdapter interface {
Poolable
OrderedMarshal(Ordered) ([]byte, error)
}
// UnmarshalAdapter behaves likes the standard library [json.Unmarshal].
type UnmarshalAdapter interface {
Poolable
Unmarshal([]byte, any) error
}
// OrderedUnmarshalAdapter behaves likes the standard library [json.Unmarshal], preserving the order of keys in objects.
type OrderedUnmarshalAdapter interface {
Poolable
OrderedUnmarshal([]byte, SetOrdered) error
}
// Adapter exposes an interface like the standard [json] library.
type Adapter interface {
MarshalAdapter
UnmarshalAdapter
OrderedAdapter
}
// OrderedAdapter exposes interfaces to process JSON and keep the order of object keys.
type OrderedAdapter interface {
OrderedMarshalAdapter
OrderedUnmarshalAdapter
NewOrderedMap(capacity int) OrderedMap
}
type Poolable interface {
// Self-redeem: for [Adapter] s that are allocated from a pool.
// The [Adapter] must not be used after calling [Redeem].
Redeem()
// Reset the state of the [Adapter], if any.
Reset()
}
@@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package ifaces
import (
"strings"
)
// Capability indicates what a JSON adapter is capable of.
type Capability uint8
const (
CapabilityMarshalJSON Capability = 1 << iota
CapabilityUnmarshalJSON
CapabilityOrderedMarshalJSON
CapabilityOrderedUnmarshalJSON
CapabilityOrderedMap
)
func (c Capability) String() string {
switch c {
case CapabilityMarshalJSON:
return "MarshalJSON"
case CapabilityUnmarshalJSON:
return "UnmarshalJSON"
case CapabilityOrderedMarshalJSON:
return "OrderedMarshalJSON"
case CapabilityOrderedUnmarshalJSON:
return "OrderedUnmarshalJSON"
case CapabilityOrderedMap:
return "OrderedMap"
default:
return "<unknown>"
}
}
// Capabilities holds several unitary capability flags
type Capabilities uint8
// Has some capability flag enabled.
func (c Capabilities) Has(capability Capability) bool {
return Capability(c)&capability > 0
}
func (c Capabilities) String() string {
var w strings.Builder
first := true
for _, capability := range []Capability{
CapabilityMarshalJSON,
CapabilityUnmarshalJSON,
CapabilityOrderedMarshalJSON,
CapabilityOrderedUnmarshalJSON,
CapabilityOrderedMap,
} {
if c.Has(capability) {
if !first {
w.WriteByte('|')
} else {
first = false
}
w.WriteString(capability.String())
}
}
return w.String()
}
const (
AllCapabilities Capabilities = Capabilities(uint8(CapabilityMarshalJSON) |
uint8(CapabilityUnmarshalJSON) |
uint8(CapabilityOrderedMarshalJSON) |
uint8(CapabilityOrderedUnmarshalJSON) |
uint8(CapabilityOrderedMap))
AllUnorderedCapabilities Capabilities = Capabilities(uint8(CapabilityMarshalJSON) | uint8(CapabilityUnmarshalJSON))
)
// RegistryEntry describes how any given adapter registers its capabilities to the [Registrar].
type RegistryEntry struct {
Who string
What Capabilities
Constructor func() Adapter
Support func(what Capability, value any) bool
}
// Registrar is a type that knows how to keep registration calls from adapters.
type Registrar interface {
RegisterFor(RegistryEntry)
}
+229
View File
@@ -0,0 +1,229 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package adapters
import (
"fmt"
"reflect"
"slices"
"sync"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
stdlib "github.com/go-openapi/swag/jsonutils/adapters/stdlib/json"
)
// Registry holds the global registry for registered adapters.
var Registry = NewRegistrar()
var (
defaultRegistered = stdlib.Register
_ ifaces.Registrar = &Registrar{}
)
type registryError string
func (e registryError) Error() string {
return string(e)
}
// ErrRegistry indicates an error returned by the [Registrar].
var ErrRegistry registryError = "JSON adapters registry error"
type registry []*ifaces.RegistryEntry
// Registrar holds registered [ifaces.Adapters] for different serialization capabilities.
//
// Internally, it maintains a cache for data types that favor a given adapter.
type Registrar struct {
marshalerRegistry registry
unmarshalerRegistry registry
orderedMarshalerRegistry registry
orderedUnmarshalerRegistry registry
orderedMapRegistry registry
gmx sync.RWMutex
// cache indexed by value type, so we don't have to lookup
marshalerCache map[reflect.Type]*ifaces.RegistryEntry
unmarshalerCache map[reflect.Type]*ifaces.RegistryEntry
orderedMarshalerCache map[reflect.Type]*ifaces.RegistryEntry
orderedUnmarshalerCache map[reflect.Type]*ifaces.RegistryEntry
orderedMapCache map[reflect.Type]*ifaces.RegistryEntry
}
func NewRegistrar() *Registrar {
r := &Registrar{}
r.marshalerRegistry = make(registry, 0, 1)
r.unmarshalerRegistry = make(registry, 0, 1)
r.orderedMarshalerRegistry = make(registry, 0, 1)
r.orderedUnmarshalerRegistry = make(registry, 0, 1)
r.orderedMapRegistry = make(registry, 0, 1)
r.marshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
r.unmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
r.orderedMarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
r.orderedUnmarshalerCache = make(map[reflect.Type]*ifaces.RegistryEntry)
r.orderedMapCache = make(map[reflect.Type]*ifaces.RegistryEntry)
defaultRegistered(r)
return r
}
// ClearCache resets the internal type cache.
func (r *Registrar) ClearCache() {
r.gmx.Lock()
r.clearCache()
r.gmx.Unlock()
}
// Reset the [Registrar] to its defaults.
func (r *Registrar) Reset() {
r.gmx.Lock()
r.clearCache()
r.marshalerRegistry = r.marshalerRegistry[:0]
r.unmarshalerRegistry = r.unmarshalerRegistry[:0]
r.orderedMarshalerRegistry = r.orderedMarshalerRegistry[:0]
r.orderedUnmarshalerRegistry = r.orderedUnmarshalerRegistry[:0]
r.orderedMapRegistry = r.orderedMapRegistry[:0]
r.gmx.Unlock()
defaultRegistered(r)
}
// RegisterFor registers an adapter for some JSON capabilities.
func (r *Registrar) RegisterFor(entry ifaces.RegistryEntry) {
r.gmx.Lock()
if entry.What.Has(ifaces.CapabilityMarshalJSON) {
e := entry
e.What &= ifaces.Capabilities(ifaces.CapabilityMarshalJSON)
r.marshalerRegistry = slices.Insert(r.marshalerRegistry, 0, &e)
}
if entry.What.Has(ifaces.CapabilityUnmarshalJSON) {
e := entry
e.What &= ifaces.Capabilities(ifaces.CapabilityUnmarshalJSON)
r.unmarshalerRegistry = slices.Insert(r.unmarshalerRegistry, 0, &e)
}
if entry.What.Has(ifaces.CapabilityOrderedMarshalJSON) {
e := entry
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMarshalJSON)
r.orderedMarshalerRegistry = slices.Insert(r.orderedMarshalerRegistry, 0, &e)
}
if entry.What.Has(ifaces.CapabilityOrderedUnmarshalJSON) {
e := entry
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedUnmarshalJSON)
r.orderedUnmarshalerRegistry = slices.Insert(r.orderedUnmarshalerRegistry, 0, &e)
}
if entry.What.Has(ifaces.CapabilityOrderedMap) {
e := entry
e.What &= ifaces.Capabilities(ifaces.CapabilityOrderedMap)
r.orderedMapRegistry = slices.Insert(r.orderedMapRegistry, 0, &e)
}
r.gmx.Unlock()
}
// AdapterFor returns an [ifaces.Adapter] that supports this capability for this type of value.
//
// The [ifaces.Adapter] may be redeemed to its pool using its Redeem() method, for adapters that support global
// pooling. When this is not the case, the redeem function is just a no-operation.
func (r *Registrar) AdapterFor(capability ifaces.Capability, value any) ifaces.Adapter {
entry := r.findFirstFor(capability, value)
if entry == nil {
return nil
}
return entry.Constructor()
}
func (r *Registrar) clearCache() {
clear(r.marshalerCache)
clear(r.unmarshalerCache)
clear(r.orderedMarshalerCache)
clear(r.orderedUnmarshalerCache)
clear(r.orderedMapCache)
}
func (r *Registrar) findFirstFor(capability ifaces.Capability, value any) *ifaces.RegistryEntry {
switch capability {
case ifaces.CapabilityMarshalJSON:
return r.findFirstInRegistryFor(r.marshalerRegistry, r.marshalerCache, capability, value)
case ifaces.CapabilityUnmarshalJSON:
return r.findFirstInRegistryFor(r.unmarshalerRegistry, r.unmarshalerCache, capability, value)
case ifaces.CapabilityOrderedMarshalJSON:
return r.findFirstInRegistryFor(r.orderedMarshalerRegistry, r.orderedMarshalerCache, capability, value)
case ifaces.CapabilityOrderedUnmarshalJSON:
return r.findFirstInRegistryFor(r.orderedUnmarshalerRegistry, r.orderedUnmarshalerCache, capability, value)
case ifaces.CapabilityOrderedMap:
return r.findFirstInRegistryFor(r.orderedMapRegistry, r.orderedMapCache, capability, value)
default:
panic(fmt.Errorf("unsupported capability %d: %w", capability, ErrRegistry))
}
}
func (r *Registrar) findFirstInRegistryFor(reg registry, cache map[reflect.Type]*ifaces.RegistryEntry, capability ifaces.Capability, value any) *ifaces.RegistryEntry {
r.gmx.RLock()
if len(reg) > 1 {
if entry, ok := cache[reflect.TypeOf(value)]; ok {
// cache hit
r.gmx.RUnlock()
return entry
}
}
for _, entry := range reg {
if !entry.Support(capability, value) {
continue
}
r.gmx.RUnlock()
// update the internal cache
r.gmx.Lock()
cache[reflect.TypeOf(value)] = entry
r.gmx.Unlock()
return entry
}
// no adapter found
r.gmx.RUnlock()
return nil
}
// MarshalAdapterFor returns the first adapter that knows how to Marshal this type of value.
func MarshalAdapterFor(value any) ifaces.MarshalAdapter {
return Registry.AdapterFor(ifaces.CapabilityMarshalJSON, value)
}
// OrderedMarshalAdapterFor returns the first adapter that knows how to OrderedMarshal this type of value.
func OrderedMarshalAdapterFor(value ifaces.Ordered) ifaces.OrderedMarshalAdapter {
return Registry.AdapterFor(ifaces.CapabilityOrderedMarshalJSON, value)
}
// UnmarshalAdapterFor returns the first adapter that knows how to Unmarshal this type of value.
func UnmarshalAdapterFor(value any) ifaces.UnmarshalAdapter {
return Registry.AdapterFor(ifaces.CapabilityUnmarshalJSON, value)
}
// OrderedUnmarshalAdapterFor provides the first adapter that knows how to OrderedUnmarshal this type of value.
func OrderedUnmarshalAdapterFor(value ifaces.SetOrdered) ifaces.OrderedUnmarshalAdapter {
return Registry.AdapterFor(ifaces.CapabilityOrderedUnmarshalJSON, value)
}
// NewOrderedMap provides the "ordered map" implementation provided by the registry.
func NewOrderedMap(capacity int) ifaces.OrderedMap {
var v any
adapter := Registry.AdapterFor(ifaces.CapabilityOrderedUnmarshalJSON, v)
if adapter == nil {
return nil
}
defer adapter.Redeem()
return adapter.NewOrderedMap(capacity)
}
func noopRedeemer() {}
@@ -0,0 +1,115 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
stdjson "encoding/json"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
"github.com/go-openapi/swag/typeutils"
)
const sensibleBufferSize = 8192
type jsonError string
func (e jsonError) Error() string {
return string(e)
}
// ErrStdlib indicates that an error comes from the stdlib JSON adapter
var ErrStdlib jsonError = "error from the JSON adapter stdlib"
var _ ifaces.Adapter = &Adapter{}
type Adapter struct {
}
// NewAdapter yields an [ifaces.Adapter] using the standard library.
func NewAdapter() *Adapter {
return &Adapter{}
}
func (a *Adapter) Marshal(value any) ([]byte, error) {
return stdjson.Marshal(value)
}
func (a *Adapter) Unmarshal(data []byte, value any) error {
return stdjson.Unmarshal(data, value)
}
func (a *Adapter) OrderedMarshal(value ifaces.Ordered) ([]byte, error) {
w := poolOfWriters.Borrow()
defer func() {
poolOfWriters.Redeem(w)
}()
if typeutils.IsNil(value) {
w.RawString("null")
return w.BuildBytes()
}
w.RawByte('{')
first := true
for k, v := range value.OrderedItems() {
if first {
first = false
} else {
w.RawByte(',')
}
w.String(k)
w.RawByte(':')
switch val := v.(type) {
case ifaces.Ordered:
w.Raw(a.OrderedMarshal(val))
default:
w.Raw(stdjson.Marshal(v))
}
}
w.RawByte('}')
return w.BuildBytes()
}
func (a *Adapter) OrderedUnmarshal(data []byte, value ifaces.SetOrdered) error {
var m MapSlice
if err := m.OrderedUnmarshalJSON(data); err != nil {
return err
}
if typeutils.IsNil(m) {
// force input value to nil
value.SetOrderedItems(nil)
return nil
}
value.SetOrderedItems(m.OrderedItems())
return nil
}
func (a *Adapter) NewOrderedMap(capacity int) ifaces.OrderedMap {
m := make(MapSlice, 0, capacity)
return &m
}
// Redeem the [Adapter] when it comes from a pool.
//
// The adapter becomes immediately unusable once redeemed.
func (a *Adapter) Redeem() {
if a == nil {
return
}
RedeemAdapter(a)
}
func (a *Adapter) Reset() {
}
@@ -0,0 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Package json implements an [ifaces.Adapter] using the standard library.
package json
@@ -0,0 +1,320 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
stdjson "encoding/json"
"errors"
"fmt"
"io"
"math"
"strconv"
"github.com/go-openapi/swag/conv"
)
type token struct {
stdjson.Token
}
func (t token) String() string {
if t == invalidToken {
return "invalid token"
}
if t == eofToken {
return "EOF"
}
return fmt.Sprintf("%v", t.Token)
}
func (t token) Kind() tokenKind {
switch t.Token.(type) {
case nil:
return tokenNull
case stdjson.Delim:
return tokenDelim
case bool:
return tokenBool
case float64:
return tokenFloat
case stdjson.Number:
return tokenNumber
case string:
return tokenString
default:
return tokenUndef
}
}
func (t token) Delim() byte {
r, ok := t.Token.(stdjson.Delim)
if !ok {
return 0
}
return byte(r)
}
type tokenKind uint8
const (
tokenUndef tokenKind = iota
tokenString
tokenNumber
tokenFloat
tokenBool
tokenNull
tokenDelim
)
var (
invalidToken = token{
Token: stdjson.Token(struct{}{}),
}
eofToken = token{
Token: stdjson.Token(&struct{}{}),
}
undefToken = token{
Token: stdjson.Token(uint8(0)),
}
)
// jlexer apes easyjson's jlexer, but uses the standard library decoder under the hood.
type jlexer struct {
buf *bytesReader
dec *stdjson.Decoder
err error
// current token
next token
// started bool
}
type bytesReader struct {
buf []byte
offset int
}
func (b *bytesReader) Reset() {
b.buf = nil
b.offset = 0
}
func (b *bytesReader) Read(p []byte) (int, error) {
if b.offset >= len(b.buf) {
return 0, io.EOF
}
n := len(p)
buf := b.buf[b.offset:]
m := len(buf)
if n >= m {
copy(p, buf)
b.offset += m
return m, nil
}
copy(p, buf[:n])
b.offset += n
return n, nil
}
var _ io.Reader = &bytesReader{}
func newLexer(data []byte) *jlexer {
l := &jlexer{
// current: undefToken,
next: undefToken,
}
l.buf = &bytesReader{
buf: data,
}
l.dec = stdjson.NewDecoder(l.buf) // unfortunately, cannot pool this
return l
}
func (l *jlexer) Reset() {
l.err = nil
l.next = undefToken
// leave l.dec and l.buf alone, since they are replaced at every Borrow
}
func (l *jlexer) Error() error {
return l.err
}
func (l *jlexer) SetErr(err error) {
l.err = err
}
func (l *jlexer) Ok() bool {
return l.err == nil
}
// NextToken consumes a token
func (l *jlexer) NextToken() token {
if !l.Ok() {
return invalidToken
}
if l.next != undefToken {
next := l.next
l.next = undefToken
return next
}
return l.fetchToken()
}
// PeekToken returns the next token without consuming it
func (l *jlexer) PeekToken() token {
if l.next == undefToken {
l.next = l.fetchToken()
}
return l.next
}
func (l *jlexer) Skip() {
_ = l.NextToken()
}
func (l *jlexer) IsDelim(c byte) bool {
if !l.Ok() {
return false
}
next := l.PeekToken()
if next.Kind() != tokenDelim {
return false
}
if next.Delim() != c {
return false
}
return true
}
func (l *jlexer) IsNull() bool {
if !l.Ok() {
return false
}
next := l.PeekToken()
return next.Kind() == tokenNull
}
func (l *jlexer) Delim(c byte) {
if !l.Ok() {
return
}
tok := l.NextToken()
if tok.Kind() != tokenDelim {
l.err = fmt.Errorf("expected a delimiter token but got '%v': %w", tok, ErrStdlib)
return
}
if tok.Delim() != c {
l.err = fmt.Errorf("expected delimiter '%q' but got '%q': %w", c, tok.Delim(), ErrStdlib)
}
}
func (l *jlexer) Null() {
if !l.Ok() {
return
}
tok := l.NextToken()
if tok.Kind() != tokenNull {
l.err = fmt.Errorf("expected a null token but got '%v': %w", tok, ErrStdlib)
}
}
func (l *jlexer) Number() any {
if !l.Ok() {
return 0
}
tok := l.NextToken()
switch tok.Kind() { //nolint:exhaustive
case tokenNumber:
n := tok.Token.(stdjson.Number).String()
f, _ := strconv.ParseFloat(n, 64)
if conv.IsFloat64AJSONInteger(f) {
return int64(math.Trunc(f))
}
return f
case tokenFloat:
f := tok.Token.(float64)
if conv.IsFloat64AJSONInteger(f) {
return int64(math.Trunc(f))
}
return f
default:
l.err = fmt.Errorf("expected a number token but got '%v': %w", tok, ErrStdlib)
return 0
}
}
func (l *jlexer) Bool() bool {
if !l.Ok() {
return false
}
tok := l.NextToken()
if tok.Kind() != tokenBool {
l.err = fmt.Errorf("expected a bool token but got '%v': %w", tok, ErrStdlib)
return false
}
return tok.Token.(bool)
}
func (l *jlexer) String() string {
if !l.Ok() {
return ""
}
tok := l.NextToken()
if tok.Kind() != tokenString {
l.err = fmt.Errorf("expected a string token but got '%v': %w", tok, ErrStdlib)
return ""
}
return tok.Token.(string)
}
// Commas and colons are elided.
func (l *jlexer) fetchToken() token {
jtok, err := l.dec.Token()
if err != nil {
if errors.Is(err, io.EOF) {
return eofToken
}
l.err = errors.Join(err, ErrStdlib)
return invalidToken
}
return token{Token: jtok}
}
@@ -0,0 +1,266 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
stdjson "encoding/json"
"fmt"
"iter"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
)
var _ ifaces.OrderedMap = &MapSlice{}
// MapSlice represents a JSON object, with the order of keys maintained.
type MapSlice []MapItem
func (s MapSlice) OrderedItems() iter.Seq2[string, any] {
return func(yield func(string, any) bool) {
for _, item := range s {
if !yield(item.Key, item.Value) {
return
}
}
}
}
func (s *MapSlice) SetOrderedItems(items iter.Seq2[string, any]) {
if items == nil {
*s = nil
return
}
m := *s
if len(m) > 0 {
// update mode
idx := make(map[string]int, len(m))
for i, item := range m {
idx[item.Key] = i
}
for k, v := range items {
idx, ok := idx[k]
if ok {
m[idx].Value = v
continue
}
m = append(m, MapItem{Key: k, Value: v})
}
*s = m
return
}
for k, v := range items {
m = append(m, MapItem{Key: k, Value: v})
}
*s = m
}
// MarshalJSON renders a [MapSlice] as JSON bytes, preserving the order of keys.
func (s MapSlice) MarshalJSON() ([]byte, error) {
return s.OrderedMarshalJSON()
}
func (s MapSlice) OrderedMarshalJSON() ([]byte, error) {
w := poolOfWriters.Borrow()
defer func() {
poolOfWriters.Redeem(w)
}()
s.marshalObject(w)
return w.BuildBytes() // this clones data, so it's okay to redeem the writer and its buffer
}
// UnmarshalJSON builds a [MapSlice] from JSON bytes, preserving the order of keys.
//
// Inner objects are unmarshaled as [MapSlice] slices and not map[string]any.
func (s *MapSlice) UnmarshalJSON(data []byte) error {
return s.OrderedUnmarshalJSON(data)
}
func (s *MapSlice) OrderedUnmarshalJSON(data []byte) error {
l := poolOfLexers.Borrow(data)
defer func() {
poolOfLexers.Redeem(l)
}()
s.unmarshalObject(l)
return l.Error()
}
func (s MapSlice) marshalObject(w *jwriter) {
if s == nil {
w.RawString("null")
return
}
w.RawByte('{')
if len(s) == 0 {
w.RawByte('}')
return
}
s[0].marshalJSON(w)
for i := 1; i < len(s); i++ {
w.RawByte(',')
s[i].marshalJSON(w)
}
w.RawByte('}')
}
func (s *MapSlice) unmarshalObject(in *jlexer) {
if in.IsNull() {
in.Skip()
return
}
in.Delim('{') // consume token
if !in.Ok() {
return
}
result := make(MapSlice, 0)
for in.Ok() && !in.IsDelim('}') {
var mi MapItem
mi.unmarshalKeyValue(in)
result = append(result, mi)
}
in.Delim('}')
if !in.Ok() {
return
}
*s = result
}
// MapItem represents the value of a key in a JSON object held by [MapSlice].
//
// Notice that [MapItem] should not be marshaled to or unmarshaled from JSON directly,
// use this type as part of a [MapSlice] when dealing with JSON bytes.
type MapItem struct {
Key string
Value any
}
func (s MapItem) marshalJSON(w *jwriter) {
w.String(s.Key)
w.RawByte(':')
w.Raw(stdjson.Marshal(s.Value))
}
func (s *MapItem) unmarshalKeyValue(in *jlexer) {
key := in.String() // consume string
value := s.asInterface(in) // consume any value, including termination tokens '}' or ']'
if !in.Ok() {
return
}
s.Key = key
s.Value = value
}
func (s *MapItem) unmarshalArray(in *jlexer) []any {
if in.IsNull() {
in.Skip()
return nil
}
in.Delim('[') // consume token
if !in.Ok() {
return nil
}
ret := make([]any, 0)
for in.Ok() && !in.IsDelim(']') {
ret = append(ret, s.asInterface(in))
}
in.Delim(']')
if !in.Ok() {
return nil
}
return ret
}
// asInterface is very much like [jlexer.Lexer.Interface], but unmarshals an object
// into a [MapSlice], not a map[string]any.
//
// We have to force parsing errors somehow, since [jlexer.Lexer] doesn't let us
// set a parsing error directly.
func (s *MapItem) asInterface(in *jlexer) any {
if !in.Ok() {
return nil
}
tok := in.PeekToken() // look-ahead what the next token looks like
kind := tok.Kind()
switch kind {
case tokenString:
return in.String() // consume string
case tokenNumber, tokenFloat:
return in.Number()
case tokenBool:
return in.Bool()
case tokenNull:
in.Null()
return nil
case tokenDelim:
switch tok.Delim() {
case '{': // not consumed yet
ret := make(MapSlice, 0)
ret.unmarshalObject(in) // consumes the terminating '}'
if in.Ok() {
return ret
}
// lexer is in an error state: will exhaust
return nil
case '[': // not consumed yet
return s.unmarshalArray(in) // consumes the terminating ']'
default:
in.SetErr(fmt.Errorf("unexpected delimiter: %v: %w", tok, ErrStdlib)) // force error
return nil
}
case tokenUndef:
fallthrough
default:
if in.Ok() {
in.SetErr(fmt.Errorf("unexpected token: %v: %w", tok, ErrStdlib)) // force error
}
return nil
}
}
@@ -0,0 +1,143 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
"encoding/json"
"sync"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
)
type adaptersPool struct {
sync.Pool
}
func (p *adaptersPool) Borrow() *Adapter {
return p.Get().(*Adapter)
}
func (p *adaptersPool) BorrowIface() ifaces.Adapter {
return p.Get().(*Adapter)
}
func (p *adaptersPool) Redeem(a *Adapter) {
p.Put(a)
}
type writersPool struct {
sync.Pool
}
func (p *writersPool) Borrow() *jwriter {
ptr := p.Get()
jw := ptr.(*jwriter)
jw.Reset()
return jw
}
func (p *writersPool) Redeem(w *jwriter) {
p.Put(w)
}
type lexersPool struct {
sync.Pool
}
func (p *lexersPool) Borrow(data []byte) *jlexer {
ptr := p.Get()
l := ptr.(*jlexer)
l.buf = poolOfReaders.Borrow(data)
l.dec = json.NewDecoder(l.buf) // cannot pool, not exposed by the encoding/json API
l.Reset()
return l
}
func (p *lexersPool) Redeem(l *jlexer) {
l.dec = nil
discard := l.buf
l.buf = nil
poolOfReaders.Redeem(discard)
p.Put(l)
}
type readersPool struct {
sync.Pool
}
func (p *readersPool) Borrow(data []byte) *bytesReader {
ptr := p.Get()
b := ptr.(*bytesReader)
b.Reset()
b.buf = data
return b
}
func (p *readersPool) Redeem(b *bytesReader) {
p.Put(b)
}
var (
poolOfAdapters = &adaptersPool{
Pool: sync.Pool{
New: func() any {
return NewAdapter()
},
},
}
poolOfWriters = &writersPool{
Pool: sync.Pool{
New: func() any {
return newJWriter()
},
},
}
poolOfLexers = &lexersPool{
Pool: sync.Pool{
New: func() any {
return newLexer(nil)
},
},
}
poolOfReaders = &readersPool{
Pool: sync.Pool{
New: func() any {
return &bytesReader{}
},
},
}
)
// BorrowAdapter borrows an [Adapter] from the pool, recycling already allocated instances.
func BorrowAdapter() *Adapter {
return poolOfAdapters.Borrow()
}
// BorrowAdapterIface borrows a stdlib [Adapter] and converts it directly
// to [ifaces.Adapter]. This is useful to avoid further allocations when
// translating the concrete type into an interface.
func BorrowAdapterIface() ifaces.Adapter {
return poolOfAdapters.BorrowIface()
}
// RedeemAdapter redeems an [Adapter] to the pool, so it may be recycled.
func RedeemAdapter(a *Adapter) {
poolOfAdapters.Redeem(a)
}
func RedeemAdapterIface(a ifaces.Adapter) {
concrete, ok := a.(*Adapter)
if ok {
poolOfAdapters.Redeem(concrete)
}
}
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
"fmt"
"reflect"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
)
func Register(dispatcher ifaces.Registrar) {
t := reflect.TypeOf(Adapter{})
dispatcher.RegisterFor(
ifaces.RegistryEntry{
Who: fmt.Sprintf("%s.%s", t.PkgPath(), t.Name()),
What: ifaces.AllCapabilities,
Constructor: BorrowAdapterIface,
Support: support,
})
}
func support(_ ifaces.Capability, _ any) bool {
return true
}
@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package json
import (
"bytes"
"encoding/json"
"strings"
)
type jwriter struct {
buf *bytes.Buffer
err error
}
func newJWriter() *jwriter {
buf := make([]byte, 0, sensibleBufferSize)
return &jwriter{buf: bytes.NewBuffer(buf)}
}
func (w *jwriter) Reset() {
w.buf.Reset()
w.err = nil
}
func (w *jwriter) RawString(s string) {
if w.err != nil {
return
}
w.buf.WriteString(s)
}
func (w *jwriter) Raw(b []byte, err error) {
if w.err != nil {
return
}
if err != nil {
w.err = err
return
}
_, _ = w.buf.Write(b)
}
func (w *jwriter) RawByte(c byte) {
if w.err != nil {
return
}
w.buf.WriteByte(c)
}
var quoteReplacer = strings.NewReplacer(`"`, `\"`, `\`, `\\`)
func (w *jwriter) String(s string) {
if w.err != nil {
return
}
// escape quotes and \
s = quoteReplacer.Replace(s)
_ = w.buf.WriteByte('"')
json.HTMLEscape(w.buf, []byte(s))
_ = w.buf.WriteByte('"')
}
// BuildBytes returns a clone of the internal buffer.
func (w *jwriter) BuildBytes() ([]byte, error) {
if w.err != nil {
return nil, w.err
}
return bytes.Clone(w.buf.Bytes()), nil
}
+92
View File
@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package jsonutils
import (
"bytes"
)
// nullJSON represents a JSON object with null type
var nullJSON = []byte("null")
const comma = byte(',')
var closers map[byte]byte
func init() {
closers = map[byte]byte{
'{': '}',
'[': ']',
}
}
// ConcatJSON concatenates multiple json objects or arrays efficiently.
//
// Note that [ConcatJSON] performs a very simple (and fast) concatenation
// operation: it does not attempt to merge objects.
func ConcatJSON(blobs ...[]byte) []byte {
if len(blobs) == 0 {
return nil
}
last := len(blobs) - 1
for blobs[last] == nil || bytes.Equal(blobs[last], nullJSON) {
// strips trailing null objects
last--
if last < 0 {
// there was nothing but "null"s or nil...
return nil
}
}
if last == 0 {
return blobs[0]
}
var opening, closing byte
var idx, a int
buf := bytes.NewBuffer(nil)
for i, b := range blobs[:last+1] {
if b == nil || bytes.Equal(b, nullJSON) {
// a null object is in the list: skip it
continue
}
if len(b) > 0 && opening == 0 { // is this an array or an object?
opening, closing = b[0], closers[b[0]]
}
if opening != '{' && opening != '[' {
continue // don't know how to concatenate non container objects
}
const minLengthIfNotEmpty = 3
if len(b) < minLengthIfNotEmpty { // yep empty but also the last one, so closing this thing
if i == last && a > 0 {
_ = buf.WriteByte(closing) // never returns err != nil
}
continue
}
idx = 0
if a > 0 { // we need to join with a comma for everything beyond the first non-empty item
_ = buf.WriteByte(comma) // never returns err != nil
idx = 1 // this is not the first or the last so we want to drop the leading bracket
}
if i != last { // not the last one, strip brackets
_, _ = buf.Write(b[idx : len(b)-1]) // never returns err != nil
} else { // last one, strip only the leading bracket
_, _ = buf.Write(b[idx:])
}
a++
}
// somehow it ended up being empty, so provide a default value
if buf.Len() == 0 && (opening == '{' || opening == '[') {
_ = buf.WriteByte(opening) // never returns err != nil
_ = buf.WriteByte(closing)
}
return buf.Bytes()
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
// Package jsonutils provides helpers to work with JSON.
//
// These utilities work with dynamic go structures to and from JSON.
package jsonutils
+116
View File
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package jsonutils
import (
"bytes"
"encoding/json"
"github.com/go-openapi/swag/jsonutils/adapters"
"github.com/go-openapi/swag/jsonutils/adapters/ifaces"
)
// WriteJSON marshals a data structure as JSON.
//
// The difference with [json.Marshal] is that it may check among several alternatives
// to do so.
//
// See [adapters.Registrar] for more details about how to configure
// multiple serialization alternatives.
//
// NOTE: to allow types that are [easyjson.Marshaler] s to use that route to process JSON,
// you now need to register the adapter for easyjson at runtime.
func WriteJSON(value any) ([]byte, error) {
if orderedMap, isOrdered := value.(ifaces.Ordered); isOrdered {
orderedMarshaler := adapters.OrderedMarshalAdapterFor(orderedMap)
if orderedMarshaler != nil {
defer orderedMarshaler.Redeem()
return orderedMarshaler.OrderedMarshal(orderedMap)
}
// no support found in registered adapters, fallback to the default (unordered) case
}
marshaler := adapters.MarshalAdapterFor(value)
if marshaler != nil {
defer marshaler.Redeem()
return marshaler.Marshal(value)
}
// no support found in registered adapters, fallback to the default standard library.
//
// This only happens when tinkering with the global registry of adapters, since the default handles all the above cases.
return json.Marshal(value) // Codecov ignore // this is a safeguard not easily simulated in tests
}
// ReadJSON unmarshals JSON data into a data structure.
//
// The difference with [json.Unmarshal] is that it may check among several alternatives
// to do so.
//
// See [adapters.Registrar] for more details about how to configure
// multiple serialization alternatives.
//
// NOTE: value must be a pointer.
//
// If the provided value implements [ifaces.SetOrdered], it is a considered an "ordered map" and [ReadJSON]
// will favor an adapter that supports the [ifaces.OrderedUnmarshal] feature, or fallback to
// an unordered behavior if none is found.
//
// NOTE: to allow types that are [easyjson.Unmarshaler] s to use that route to process JSON,
// you now need to register the adapter for easyjson at runtime.
func ReadJSON(data []byte, value any) error {
trimmedData := bytes.Trim(data, "\x00")
if orderedMap, isOrdered := value.(ifaces.SetOrdered); isOrdered {
// if the value is an ordered map, favors support for OrderedUnmarshal.
orderedUnmarshaler := adapters.OrderedUnmarshalAdapterFor(orderedMap)
if orderedUnmarshaler != nil {
defer orderedUnmarshaler.Redeem()
return orderedUnmarshaler.OrderedUnmarshal(trimmedData, orderedMap)
}
// no support found in registered adapters, fallback to the default (unordered) case
}
unmarshaler := adapters.UnmarshalAdapterFor(value)
if unmarshaler != nil {
defer unmarshaler.Redeem()
return unmarshaler.Unmarshal(trimmedData, value)
}
// no support found in registered adapters, fallback to the default standard library.
//
// This only happens when tinkering with the global registry of adapters, since the default handles all the above cases.
return json.Unmarshal(trimmedData, value) // Codecov ignore // this is a safeguard not easily simulated in tests
}
// FromDynamicJSON turns a go value into a properly JSON typed structure.
//
// "Dynamic JSON" refers to what you get when unmarshaling JSON into an untyped any,
// i.e. objects are represented by map[string]any, arrays by []any, and
// all numbers are represented as float64.
//
// NOTE: target must be a pointer.
//
// # Maintaining the order of keys in objects
//
// If source and target implement [ifaces.Ordered] and [ifaces.SetOrdered] respectively,
// they are considered "ordered maps" and the order of keys is maintained in the
// "jsonification" process. In that case, map[string]any values are replaced by (ordered) [JSONMapSlice] ones.
func FromDynamicJSON(source, target any) error {
b, err := WriteJSON(source)
if err != nil {
return err
}
return ReadJSON(b, target)
}
+114
View File
@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0
package jsonutils
import (
"iter"
"github.com/go-openapi/swag/jsonutils/adapters"
"github.com/go-openapi/swag/typeutils"
)
// JSONMapSlice represents a JSON object, with the order of keys maintained.
//
// It behaves like an ordered map, but keys can't be accessed in constant time.
type JSONMapSlice []JSONMapItem
// OrderedItems iterates over all (key,value) pairs with the order of keys maintained.
//
// This implements the [ifaces.Ordered] interface, so that [ifaces.Adapter] s know how to marshal
// keys in the desired order.
func (s JSONMapSlice) OrderedItems() iter.Seq2[string, any] {
return func(yield func(string, any) bool) {
for _, item := range s {
if !yield(item.Key, item.Value) {
return
}
}
}
}
// SetOrderedItems sets keys in the [JSONMapSlice] objects, as presented by
// the provided iterator.
//
// As a special case, if items is nil, this sets to receiver to a nil slice.
//
// This implements the [ifaces.SetOrdered] interface, so that [ifaces.Adapter] s know how to unmarshal
// keys in the desired order.
func (s *JSONMapSlice) SetOrderedItems(items iter.Seq2[string, any]) {
if items == nil {
// force receiver to be a nil slice
*s = nil
return
}
m := *s
if len(m) > 0 {
// update mode: short-circuited when unmarshaling fresh data structures
idx := make(map[string]int, len(m))
for i, item := range m {
idx[item.Key] = i
}
for k, v := range items {
idx, ok := idx[k]
if ok {
m[idx].Value = v
continue
}
m = append(m, JSONMapItem{Key: k, Value: v})
}
*s = m
return
}
for k, v := range items {
m = append(m, JSONMapItem{Key: k, Value: v})
}
*s = m
}
// MarshalJSON renders a [JSONMapSlice] as JSON bytes, preserving the order of keys.
//
// It will pick the JSON library currently configured by the [adapters.Registry] (defaults to the standard library).
func (s JSONMapSlice) MarshalJSON() ([]byte, error) {
orderedMarshaler := adapters.OrderedMarshalAdapterFor(s)
defer orderedMarshaler.Redeem()
return orderedMarshaler.OrderedMarshal(s)
}
// UnmarshalJSON builds a [JSONMapSlice] from JSON bytes, preserving the order of keys.
//
// Inner objects are unmarshaled as ordered [JSONMapSlice] slices and not map[string]any.
//
// It will pick the JSON library currently configured by the [adapters.Registry] (defaults to the standard library).
func (s *JSONMapSlice) UnmarshalJSON(data []byte) error {
if typeutils.IsNil(*s) {
// allow to unmarshal with a simple var declaration (nil slice)
*s = JSONMapSlice{}
}
orderedUnmarshaler := adapters.OrderedUnmarshalAdapterFor(s)
defer orderedUnmarshaler.Redeem()
return orderedUnmarshaler.OrderedUnmarshal(data, s)
}
// JSONMapItem represents the value of a key in a JSON object held by [JSONMapSlice].
//
// Notice that JSONMapItem should not be marshaled to or unmarshaled from JSON directly.
//
// Use this type as part of a [JSONMapSlice] when dealing with JSON bytes.
type JSONMapItem struct {
Key string
Value any
}