github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/parser/lowercase_camel_json.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package parser
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/json"
    19  	"regexp"
    20  	"unicode"
    21  	"unicode/utf8"
    22  
    23  	"github.com/gohugoio/hugo/common/hreflect"
    24  )
    25  
    26  // Regexp definitions
    27  var (
    28  	keyMatchRegex    = regexp.MustCompile(`\"(\w+)\":`)
    29  	wordBarrierRegex = regexp.MustCompile(`(\w)([A-Z])`)
    30  )
    31  
    32  // Code adapted from https://gist.github.com/piersy/b9934790a8892db1a603820c0c23e4a7
    33  type LowerCaseCamelJSONMarshaller struct {
    34  	Value any
    35  }
    36  
    37  func (c LowerCaseCamelJSONMarshaller) MarshalJSON() ([]byte, error) {
    38  	marshalled, err := json.Marshal(c.Value)
    39  
    40  	converted := keyMatchRegex.ReplaceAllFunc(
    41  		marshalled,
    42  		func(match []byte) []byte {
    43  			// Attributes on the form XML, JSON etc.
    44  			if bytes.Equal(match, bytes.ToUpper(match)) {
    45  				return bytes.ToLower(match)
    46  			}
    47  
    48  			// Empty keys are valid JSON, only lowercase if we do not have an
    49  			// empty key.
    50  			if len(match) > 2 {
    51  				// Decode first rune after the double quotes
    52  				r, width := utf8.DecodeRune(match[1:])
    53  				r = unicode.ToLower(r)
    54  				utf8.EncodeRune(match[1:width+1], r)
    55  			}
    56  			return match
    57  		},
    58  	)
    59  
    60  	return converted, err
    61  }
    62  
    63  type ReplacingJSONMarshaller struct {
    64  	Value any
    65  
    66  	KeysToLower bool
    67  	OmitEmpty   bool
    68  }
    69  
    70  func (c ReplacingJSONMarshaller) MarshalJSON() ([]byte, error) {
    71  	converted, err := json.Marshal(c.Value)
    72  
    73  	if c.KeysToLower {
    74  		converted = keyMatchRegex.ReplaceAllFunc(
    75  			converted,
    76  			func(match []byte) []byte {
    77  				return bytes.ToLower(match)
    78  			},
    79  		)
    80  	}
    81  
    82  	if c.OmitEmpty {
    83  		// It's tricky to do this with a regexp, so convert it to a map, remove zero values and convert back.
    84  		var m map[string]interface{}
    85  		err = json.Unmarshal(converted, &m)
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		var removeZeroVAlues func(m map[string]any)
    90  		removeZeroVAlues = func(m map[string]any) {
    91  			for k, v := range m {
    92  				if !hreflect.IsTruthful(v) {
    93  					delete(m, k)
    94  				} else {
    95  					switch v.(type) {
    96  					case map[string]interface{}:
    97  						removeZeroVAlues(v.(map[string]any))
    98  					case []interface{}:
    99  						for _, vv := range v.([]interface{}) {
   100  							if m, ok := vv.(map[string]any); ok {
   101  								removeZeroVAlues(m)
   102  							}
   103  						}
   104  					}
   105  
   106  				}
   107  
   108  			}
   109  		}
   110  		removeZeroVAlues(m)
   111  		converted, err = json.Marshal(m)
   112  
   113  	}
   114  
   115  	return converted, err
   116  }