Merge pull request #44026 from nikinath/precision-json

Automatic merge from submit-queue (batch tested with PRs 44424, 44026, 43939, 44386, 42914)

Preserve int data when unmarshalling for TPR

**What this PR does / why we need it**:

The Go json package converts all numbers to float64 while unmarshalling.
This exposes many of the int64 fields to corruption when marshalled back to json.

The json package provided by kubernetes also provides a way to defer conversion of numbers
(https://golang.org/pkg/encoding/json/#Decoder.UseNumber) and does the conversions to int or float.

This is also implemented in the custom json package. See:
(https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/json/json.go)

Now, the number is preserved as an integer till the highest int64 number - `9223372036854775807`.

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #30213

**Special notes for your reviewer**: See also https://github.com/kubernetes/kubernetes/pull/16964

**Release note**:

```
NONE
```
pull/6/head
Kubernetes Submit Queue 2017-04-13 22:07:05 -07:00 committed by GitHub
commit 9ef911edec
3 changed files with 52 additions and 24 deletions

View File

@ -32,6 +32,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/util/json",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/util/yaml",
"//vendor:k8s.io/apimachinery/pkg/watch",

View File

@ -18,7 +18,7 @@ package thirdpartyresourcedata
import (
"bytes"
"encoding/json"
gojson "encoding/json"
"fmt"
"io"
"net/url"
@ -28,6 +28,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubernetes/pkg/api"
apiutil "k8s.io/kubernetes/pkg/api/util"
@ -233,14 +234,11 @@ func NewDecoder(delegate runtime.Decoder, kind string) runtime.Decoder {
var _ runtime.Decoder = &thirdPartyResourceDataDecoder{}
func parseObject(data []byte) (map[string]interface{}, error) {
var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil {
var mapObj map[string]interface{}
if err := json.Unmarshal(data, &mapObj); err != nil {
return nil, err
}
mapObj, ok := obj.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unexpected object: %#v", obj)
}
return mapObj, nil
}
@ -297,6 +295,7 @@ func (t *thirdPartyResourceDataDecoder) populateResource(objIn *extensions.Third
if err := json.Unmarshal(metadataData, &objIn.ObjectMeta); err != nil {
return err
}
// Override API Version with the ThirdPartyResourceData value
// TODO: fix this hard code
objIn.APIVersion = v1beta1.SchemeGroupVersion.String()
@ -372,15 +371,11 @@ func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *schema.GroupVer
}
thirdParty := into.(*extensions.ThirdPartyResourceData)
var dataObj interface{}
if err := json.Unmarshal(data, &dataObj); err != nil {
var mapObj map[string]interface{}
if err := json.Unmarshal(data, &mapObj); err != nil {
return nil, nil, err
}
mapObj, ok := dataObj.(map[string]interface{})
if !ok {
return nil, nil, fmt.Errorf("unexpected object: %#v", dataObj)
}
/*if gvk.Kind != "ThirdPartyResourceData" {
return nil, nil, fmt.Errorf("unexpected kind: %s", gvk.Kind)
}*/
@ -466,14 +461,10 @@ func NewEncoder(delegate runtime.Encoder, gvk schema.GroupVersionKind) runtime.E
var _ runtime.Encoder = &thirdPartyResourceDataEncoder{}
func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error {
var objOut interface{}
if err := json.Unmarshal(obj.Data, &objOut); err != nil {
var objMap map[string]interface{}
if err := json.Unmarshal(obj.Data, &objMap); err != nil {
return err
}
objMap, ok := objOut.(map[string]interface{})
if !ok {
return fmt.Errorf("unexpected type: %v", objOut)
}
objMap["metadata"] = &obj.ObjectMeta
encoder := json.NewEncoder(stream)
@ -486,7 +477,7 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri
return encodeToJSON(obj, stream)
case *extensions.ThirdPartyResourceDataList:
// TODO: There are likely still better ways to do this...
listItems := make([]json.RawMessage, len(obj.Items))
listItems := make([]gojson.RawMessage, len(obj.Items))
for ix := range obj.Items {
buff := &bytes.Buffer{}
@ -494,7 +485,7 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri
if err != nil {
return err
}
listItems[ix] = json.RawMessage(buff.Bytes())
listItems[ix] = gojson.RawMessage(buff.Bytes())
}
if t.gvk.Empty() {
@ -503,8 +494,8 @@ func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Wri
encMap := struct {
// +optional
Kind string `json:"kind,omitempty"`
Items []json.RawMessage `json:"items"`
Kind string `json:"kind,omitempty"`
Items []gojson.RawMessage `json:"items"`
// +optional
Metadata metav1.ListMeta `json:"metadata,omitempty"`
// +optional

View File

@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"time"
@ -289,5 +290,40 @@ func TestThirdPartyResourceDataListEncoding(t *testing.T) {
if targetOutput.APIVersion != gv.String() {
t.Errorf("apiversion mismatch %v != %v", targetOutput.APIVersion, gv.String())
}
}
func TestDecodeNumbers(t *testing.T) {
gv := schema.GroupVersion{Group: "stable.foo.faz", Version: "v1"}
gvk := gv.WithKind("Foo")
e := &thirdPartyResourceDataEncoder{delegate: testapi.Extensions.Codec(), gvk: gvk}
d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()}
// Use highest int64 number and 1000000.
subject := &extensions.ThirdPartyResourceDataList{
Items: []extensions.ThirdPartyResourceData{
{
Data: []byte(`{"num1": 9223372036854775807, "num2": 1000000}`),
},
},
}
// Encode to get original JSON.
originalJSON := bytes.NewBuffer([]byte{})
err := e.Encode(subject, originalJSON)
if err != nil {
t.Errorf("encoding unexpected error: %v", err)
}
// Decode original JSON.
var into runtime.Object
into, _, err = d.Decode(originalJSON.Bytes(), &gvk, into)
if err != nil {
t.Errorf("decoding unexpected error: %v", err)
}
// Check if int is preserved.
decodedJSON := into.(*extensions.ThirdPartyResourceDataList).Items[0].Data
if !strings.Contains(string(decodedJSON), `"num1":9223372036854775807,"num2":1000000`) {
t.Errorf("Expected %s, got %s", `"num1":9223372036854775807,"num2":1000000`, string(decodedJSON))
}
}