1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2023 by the Nominatim developer community.
 
   6 # For a full list of authors see the git log.
 
   8 Streaming JSON encoder.
 
  10 from typing import Any, TypeVar, Optional, Callable
 
  14 except ModuleNotFoundError:
 
  15     import json # type: ignore[no-redef]
 
  17 T = TypeVar('T') # pylint: disable=invalid-name
 
  20     """ JSON encoder that renders the output directly into an output
 
  21         stream. This is a very simple writer which produces JSON in a
 
  22         compact as possible form.
 
  24         The writer does not check for syntactic correctness. It is the
 
  25         responsibility of the caller to call the write functions in an
 
  26         order that produces correct JSON.
 
  28         All functions return the writer object itself so that function
 
  32     def __init__(self) -> None:
 
  33         self.data = io.StringIO()
 
  37     def __call__(self) -> str:
 
  38         """ Return the rendered JSON content as a string.
 
  39             The writer remains usable after calling this function.
 
  42             assert self.pending in (']', '}')
 
  43             self.data.write(self.pending)
 
  45         return self.data.getvalue()
 
  48     def start_object(self) -> 'JsonWriter':
 
  49         """ Write the open bracket of a JSON object.
 
  52             self.data.write(self.pending)
 
  57     def end_object(self) -> 'JsonWriter':
 
  58         """ Write the closing bracket of a JSON object.
 
  60         assert self.pending in (',', '{', '')
 
  61         if self.pending == '{':
 
  62             self.data.write(self.pending)
 
  67     def start_array(self) -> 'JsonWriter':
 
  68         """ Write the opening bracket of a JSON array.
 
  71             self.data.write(self.pending)
 
  76     def end_array(self) -> 'JsonWriter':
 
  77         """ Write the closing bracket of a JSON array.
 
  79         assert self.pending in (',', '[', '')
 
  80         if self.pending == '[':
 
  81             self.data.write(self.pending)
 
  86     def key(self, name: str) -> 'JsonWriter':
 
  87         """ Write the key string of a JSON object.
 
  90         self.data.write(self.pending)
 
  91         self.data.write(json.dumps(name, ensure_ascii=False))
 
  96     def value(self, value: Any) -> 'JsonWriter':
 
  97         """ Write out a value as JSON. The function uses the json.dumps()
 
  98             function for encoding the JSON. Thus any value that can be
 
  99             encoded by that function is permissible here.
 
 101         return self.raw(json.dumps(value, ensure_ascii=False))
 
 104     def next(self) -> 'JsonWriter':
 
 105         """ Write out a delimiter comma between JSON object or array elements.
 
 108             self.data.write(self.pending)
 
 113     def raw(self, raw_json: str) -> 'JsonWriter':
 
 114         """ Write out the given value as is. This function is useful if
 
 115             a value is already available in JSON format.
 
 118             self.data.write(self.pending)
 
 120         self.data.write(raw_json)
 
 124     def keyval(self, key: str, value: Any) -> 'JsonWriter':
 
 125         """ Write out an object element with the given key and value.
 
 126             This is a shortcut for calling 'key()', 'value()' and 'next()'.
 
 133     def keyval_not_none(self, key: str, value: Optional[T],
 
 134                         transform: Optional[Callable[[T], Any]] = None) -> 'JsonWriter':
 
 135         """ Write out an object element only if the value is not None.
 
 136             If 'transform' is given, it must be a function that takes the
 
 137             value type and returns a JSON encodable type. The transform
 
 138             function will be called before the value is written out.
 
 140         if value is not None:
 
 142             self.value(transform(value) if transform else value)