Source file src/encoding/json/v2/example_orderedobject_test.go

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json_test
     8  
     9  import (
    10  	"fmt"
    11  	"log"
    12  	"reflect"
    13  
    14  	"encoding/json/jsontext"
    15  	"encoding/json/v2"
    16  )
    17  
    18  // OrderedObject is an ordered sequence of name/value members in a JSON object.
    19  //
    20  // RFC 8259 defines an object as an "unordered collection".
    21  // JSON implementations need not make "ordering of object members visible"
    22  // to applications nor will they agree on the semantic meaning of an object if
    23  // "the names within an object are not unique". For maximum compatibility,
    24  // applications should avoid relying on ordering or duplicity of object names.
    25  type OrderedObject[V any] []ObjectMember[V]
    26  
    27  // ObjectMember is a JSON object member.
    28  type ObjectMember[V any] struct {
    29  	Name  string
    30  	Value V
    31  }
    32  
    33  // MarshalJSONTo encodes obj as a JSON object into enc.
    34  func (obj *OrderedObject[V]) MarshalJSONTo(enc *jsontext.Encoder) error {
    35  	if err := enc.WriteToken(jsontext.BeginObject); err != nil {
    36  		return err
    37  	}
    38  	for i := range *obj {
    39  		member := &(*obj)[i]
    40  		if err := json.MarshalEncode(enc, &member.Name); err != nil {
    41  			return err
    42  		}
    43  		if err := json.MarshalEncode(enc, &member.Value); err != nil {
    44  			return err
    45  		}
    46  	}
    47  	if err := enc.WriteToken(jsontext.EndObject); err != nil {
    48  		return err
    49  	}
    50  	return nil
    51  }
    52  
    53  // UnmarshalJSONFrom decodes a JSON object from dec into obj.
    54  func (obj *OrderedObject[V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
    55  	if k := dec.PeekKind(); k != '{' {
    56  		// The [json] package automatically populates relevant fields
    57  		// in a [json.SemanticError] to provide additional context.
    58  		return &json.SemanticError{JSONKind: k}
    59  	}
    60  	if _, err := dec.ReadToken(); err != nil {
    61  		return err
    62  	}
    63  	for dec.PeekKind() != '}' {
    64  		*obj = append(*obj, ObjectMember[V]{})
    65  		member := &(*obj)[len(*obj)-1]
    66  		if err := json.UnmarshalDecode(dec, &member.Name); err != nil {
    67  			return err
    68  		}
    69  		if err := json.UnmarshalDecode(dec, &member.Value); err != nil {
    70  			return err
    71  		}
    72  	}
    73  	if _, err := dec.ReadToken(); err != nil {
    74  		return err
    75  	}
    76  	return nil
    77  }
    78  
    79  // The exact order of JSON object can be preserved through the use of a
    80  // specialized type that implements [MarshalerTo] and [UnmarshalerFrom].
    81  func Example_orderedObject() {
    82  	// Round-trip marshal and unmarshal an ordered object.
    83  	// We expect the order and duplicity of JSON object members to be preserved.
    84  	// Specify jsontext.AllowDuplicateNames since this object contains "fizz" twice.
    85  	want := OrderedObject[string]{
    86  		{"fizz", "buzz"},
    87  		{"hello", "world"},
    88  		{"fizz", "wuzz"},
    89  	}
    90  	b, err := json.Marshal(&want, jsontext.AllowDuplicateNames(true))
    91  	if err != nil {
    92  		log.Fatal(err)
    93  	}
    94  	var got OrderedObject[string]
    95  	err = json.Unmarshal(b, &got, jsontext.AllowDuplicateNames(true))
    96  	if err != nil {
    97  		log.Fatal(err)
    98  	}
    99  
   100  	// Sanity check.
   101  	if !reflect.DeepEqual(got, want) {
   102  		log.Fatalf("roundtrip mismatch: got %v, want %v", got, want)
   103  	}
   104  
   105  	// Print the serialized JSON object.
   106  	(*jsontext.Value)(&b).Indent() // indent for readability
   107  	fmt.Println(string(b))
   108  
   109  	// Output:
   110  	// {
   111  	// 	"fizz": "buzz",
   112  	// 	"hello": "world",
   113  	// 	"fizz": "wuzz"
   114  	// }
   115  }
   116  

View as plain text