Home > Back-end >  check json array length without unmarshalling
check json array length without unmarshalling

Time:01-08

Ive go a request body that is an json array of objects something like,

    {
        "data": [
            {
                "id": "1234",
                "someNestedObject": {
                    "someBool": true,
                    "randomNumber": 488
                },
                "timestamp": "2021-12-13T02:43:44.155Z"
            },
            {
                "id": "4321",
                "someNestedObject": {
                    "someBool": false,
                    "randomNumber": 484
                },
                "timestamp": "2018-11-13T02:43:44.155Z"
            }
        ]
    }

I want to get a count of the objects in the array and split them into seperate json outputs to pass onto the next service. Im doing this atm by unmarshalling the original json request body and and then looping over the the elements marshalling each one again and attaching it to whatever outgoing message is being sent. Something like,

requestBodyBytes := []bytes(JSON_INPUT_STRING)

type body struct {
    Foo []json.RawMessage `json:"foo"`
}

var inputs body

_ = json.Unmarshal(requestBodyBytes, &inputs)

for input := range inputs {
    re, _ := json.Marshal(m)

    ... do something with re
}

What Im seeing though is the byte array of the before and after is different, even though the string representation is the same. I am wondering if there is a way to do this without altering the encoding or whatever is happening here to change the bytes to safeguard against any unwanted mutations? The actual json objects in the array will all have different shapes so I cant use a structured json definition with field validations to help.

Also, the above code is just an example of whats happening so if there are spelling or syntax errors please ignore them as the actual code works as described.

CodePudding user response:

If you use json.RawMessage, the JSON source text will not be parsed but stored in it as-is (it's a []byte).

So if you want to distribute the same JSON array element, you do not need to do anything with it, you may "hand it over" as-is. You do not have to pass it to json.Marshal(), it's already JSON marshalled text.

So simply do:

for _, input := range inputs.Foo {
    // input is of type json.RawMessage, and it's already JSON text
}

If you pass a json.RawMessage to json.Marshal(), it might get reencoded, e.g. compacted (which may result in a different byte sequence, but it will hold the same data as JSON).

Compacting might even be a good idea, as the original indentation might look weird taken out of the original context (object and array), also it'll be shorter. To simply compact a JSON text, you may use json.Compact() like this:

for _, input := range inputs.Foo {
    buf := &bytes.Buffer{}
    if err := json.Compact(buf, input); err != nil {
        panic(err)
    }
    fmt.Println(buf) // The compacted array element value
}

If you don't want to compact it but to indent the array elements on their own, use json.Indent() like this:

for _, input := range inputs.Foo {
    buf := &bytes.Buffer{}
    if err := json.Indent(buf, input, "", "  "); err != nil {
        panic(err)
    }
    fmt.Println(buf)
}

Using your example input, this is how the first array element would look like (original, compacted and indented):

Orignal:
{
            "id": "1234",
            "someNestedObject": {
                "someBool": true,
                "randomNumber": 488
            },
            "timestamp": "2021-12-13T02:43:44.155Z"
        }

Compacted:
{"id":"1234","someNestedObject":{"someBool":true,"randomNumber":488},"timestamp":"2021-12-13T02:43:44.155Z"}

Indented:
{
  "id": "1234",
  "someNestedObject": {
    "someBool": true,
    "randomNumber": 488
  },
  "timestamp": "2021-12-13T02:43:44.155Z"
}

Try the examples on the Go Playground.

Also note that if you do decide to compact or indent the individual array elements in the loop, you may create a simple bytes.Buffer before the loop, and reuse it in each iteration, calling its Buffer.Reset() method to clear the previous array's data.

It could look like this:

buf := &bytes.Buffer{}
for _, input := range inputs.Foo {
    buf.Reset()
    if err := json.Compact(buf, input); err != nil {
        panic(err)
    }
    fmt.Println("Compacted:\n", buf)
}
  •  Tags:  
  • Related