Skip to content

Commit

Permalink
Merge pull request #44026 from nikinath/precision-json
Browse files Browse the repository at this point in the history
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 #16964

**Release note**:

```
NONE
```
  • Loading branch information
Kubernetes Submit Queue committed Apr 14, 2017
2 parents 09fec90 + eb88c4b commit 9ef911e
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 23 deletions.
1 change: 1 addition & 0 deletions pkg/registry/extensions/thirdpartyresourcedata/BUILD
Expand Up @@ -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",
Expand Down
37 changes: 14 additions & 23 deletions pkg/registry/extensions/thirdpartyresourcedata/codec.go
Expand Up @@ -18,7 +18,7 @@ package thirdpartyresourcedata

import (
"bytes"
"encoding/json"
gojson "encoding/json"
"fmt"
"io"
"net/url"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}*/
Expand Down Expand Up @@ -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)
Expand All @@ -486,15 +477,15 @@ 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{}
err := encodeToJSON(&obj.Items[ix], buff)
if err != nil {
return err
}
listItems[ix] = json.RawMessage(buff.Bytes())
listItems[ix] = gojson.RawMessage(buff.Bytes())
}

if t.gvk.Empty() {
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions pkg/registry/extensions/thirdpartyresourcedata/codec_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -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))
}
}

0 comments on commit 9ef911e

Please sign in to comment.