mirror of
https://github.com/kubernetes/node-problem-detector.git
synced 2026-08-23 22:26:27 +00:00
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:
committed by
Ciprian Hacman
parent
255c6e602c
commit
97bb2fbb44
+202
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
+91
@@ -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
@@ -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() {}
|
||||
+115
@@ -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() {
|
||||
}
|
||||
+5
@@ -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
|
||||
+320
@@ -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}
|
||||
}
|
||||
+266
@@ -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
|
||||
}
|
||||
}
|
||||
+143
@@ -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)
|
||||
}
|
||||
}
|
||||
+26
@@ -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
|
||||
}
|
||||
+75
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user