Skip to content

How to piecemeal stream write/encode a single json object? #23

Closed
@veqryn

Description

@veqryn

I would like to piecemeal construct a single json object or array.
Ideally, I would like to be able to clone the Encoder at any point, so that I can have multiple versions of the partially finished json.
The main use case right now, is a slog.Handler that will be able to partially write the json as attributes are added, so that it doesn't need to fully marshal all of the attributes every time (similar to the built-in slog handlers, but using this json v2 library so I can take advantage of the new encoder options like SpaceAfterComma).

Example attempt:

package main

import (
	"bytes"
	"fmt"

	"github.com/go-json-experiment/json"
	"github.com/go-json-experiment/json/jsontext"
)

type Property struct {
	Name  string
	Value any
}

func main() {
	properties := []Property{
		{"foo", "bar"},
		{"num", 12},
		{"hi", Hello{Foo: "fooo", Bar: 34.56, Earth: World{Baz: "bazz", Nuu: 78}}},
	}

	opts := []jsontext.Options{
		json.Deterministic(true),
		jsontext.SpaceAfterComma(true),
	}

	buf := &bytes.Buffer{}
	encoder := jsontext.NewEncoder(buf, opts...)

	wToken(encoder, jsontext.ObjectStart)

	for _, p := range properties {
		wValue(encoder, marshal(p.Name))
		wValue(encoder, marshal(p.Value))
	}

	wToken(encoder, jsontext.ObjectEnd)

	fmt.Println(buf.String())
}

func marshal(in any, opts ...json.Options) jsontext.Value {
	b, err := json.Marshal(in, opts...)
	if err != nil {
		panic(err)
	}
	return jsontext.Value(b)
}

func wToken(encoder *jsontext.Encoder, token jsontext.Token) {
	if err := encoder.WriteToken(token); err != nil {
		panic(err)
	}
}

func wValue(encoder *jsontext.Encoder, value jsontext.Value) {
	if err := encoder.WriteValue(value); err != nil {
		panic(err)
	}
}

type Hello struct {
	Foo   string
	Bar   float64
	Earth World
}

type World struct {
	Baz string
	Nuu int
}

Right now, I am encountering a few problems:

  1. The values are being parsed twice.
    In the example above, I am using the regular json.Marshal(...) to turn an any into a jsontext.Value, then writing that value to the encoder.
    When writing to the encoder, it automatically re-parses the []byte value to confirm it is valid json. This is unneeded and a performance penalty.

  2. I don't see a way to clone the Encoder.
    There isn't a method to create a new encoder with an existing buffer or any of the encoder's state set.
    If I choose to replace the encoder with a simple buffer, I would lose out on all the encoder's guarantees and the jsontext.Options available, or have to re-implement them myself.

Is there an existing better way to do this?

If not, could we discuss what api additions or changes would be needed to allow this use case?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions