DAW JSON Link
daw_json_link_impl.h
Go to the documentation of this file.
1 // Copyright (c) Darrell Wright
2 //
3 // Distributed under the Boost Software License, Version 1.0. (See accompanying
4 // file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
5 //
6 // Official repository: https://github.com/beached/daw_json_link
7 //
8 
9 #pragma once
10 
11 #include "daw_json_arrow_proxy.h"
12 #include "daw_json_assert.h"
13 #include "daw_json_parse_common.h"
15 #include "daw_json_parse_value.h"
17 #include "daw_json_to_string.h"
18 #include "daw_murmur3.h"
19 
20 #include <daw/daw_algorithm.h>
21 #include <daw/daw_cxmath.h>
22 #include <daw/daw_parser_helper_sv.h>
23 #include <daw/daw_sort_n.h>
24 #include <daw/daw_string_view.h>
25 #include <daw/daw_traits.h>
26 #include <daw/daw_utility.h>
27 #include <daw/iterator/daw_back_inserter.h>
28 #include <daw/iterator/daw_inserter.h>
29 
30 #include <array>
31 #include <chrono>
32 #include <ciso646>
33 #include <cstdint>
34 #include <cstdlib>
35 #include <iterator>
36 #include <optional>
37 #include <string_view>
38 #include <tuple>
39 #include <type_traits>
40 #include <utility>
41 
42 namespace daw::json {
43  template<JsonNullable>
45 
46  template<>
48  using result_type = std::chrono::time_point<std::chrono::system_clock,
49  std::chrono::milliseconds>;
50 
51  [[maybe_unused, nodiscard]] inline constexpr result_type
52  operator( )( char const *ptr, std::size_t sz ) const {
53  return datetime::parse_iso8601_timestamp( daw::string_view( ptr, sz ) );
54  }
55  };
56 
57  template<>
59  using result_type =
60  std::optional<std::chrono::time_point<std::chrono::system_clock,
61  std::chrono::milliseconds>>;
62 
63  [[maybe_unused, nodiscard]] inline constexpr result_type
64  operator( )( ) const {
65  return { };
66  }
67 
68  [[maybe_unused, nodiscard]] inline constexpr result_type
69  operator( )( char const *ptr, std::size_t sz ) const {
70  return datetime::parse_iso8601_timestamp( daw::string_view( ptr, sz ) );
71  }
72  };
73 
74  template<typename T>
76  [[nodiscard]] inline constexpr decltype( auto ) operator( )( ) {
77  if constexpr( std::is_same_v<T, std::string_view> or
78  std::is_same_v<T, std::optional<std::string_view>> ) {
79  return std::string_view{ };
80  } else {
81  return from_string( daw::tag<T> );
82  }
83  }
84 
85  [[nodiscard]] inline constexpr decltype( auto )
86  operator( )( std::string_view sv ) {
87  if constexpr( std::is_same_v<T, std::string_view> or
88  std::is_same_v<T, std::optional<std::string_view>> ) {
89  return sv;
90  } else {
91  return from_string( daw::tag<T>, sv );
92  }
93  }
94  };
95 } // namespace daw::json
96 
97 namespace daw::json::json_details {
98  template<typename, std::size_t Lhs, std::size_t Rhs>
99  struct are_equal : std::bool_constant<Lhs == Rhs> {};
100 
101  template<typename Container, typename Value>
102  using can_insert1_test =
103  decltype( std::declval<Container &>( ).insert( std::declval<Value>( ) ) );
104 
105  template<typename Container, typename Value>
106  inline constexpr bool can_insert1_v =
107  daw::is_detected_v<can_insert1_test, Container, Value>;
108 
109  template<bool HashesCollide, typename Range>
110  struct location_info_t {
111  UInt32 hash_value = 0_u32;
112  daw::string_view const *name;
113  Range location{ };
114  std::size_t count = 0;
115 
116  explicit constexpr location_info_t( daw::string_view const *Name )
117  : hash_value( daw::name_hash( *Name ) )
118  , name( Name ) {}
119 
120  [[maybe_unused, nodiscard]] inline constexpr bool missing( ) const {
121  return location.is_null( );
122  }
123  };
124 
125  template<typename Range>
126  struct location_info_t<false, Range> {
127  UInt32 hash_value = 0_u32;
128  typename Range::without_allocator_type location{ };
129  std::size_t count = 0;
130 
131  explicit constexpr location_info_t( daw::string_view const *Name )
132  : hash_value( daw::name_hash( *Name ) ) {}
133 
134  [[maybe_unused, nodiscard]] inline constexpr bool missing( ) const {
135  return location.is_null( );
136  }
137  };
138 
139  /***
140  * Contains an array of member location_info mapped in a json_class
141  * @tparam N Number of mapped members from json_class
142  * @tparam Range see IteratorRange
143  */
144  template<std::size_t N, typename Range, bool HasCollisions = true>
145  struct locations_info_t {
146  using value_type = location_info_t<HasCollisions, Range>;
147  static constexpr bool has_collisons = HasCollisions;
148  std::array<value_type, N> names;
149 
150  constexpr location_info_t<HasCollisions, Range> const &
151  operator[]( std::size_t idx ) const {
152  return names[idx];
153  }
154 
155  constexpr location_info_t<HasCollisions, Range> &
156  operator[]( std::size_t idx ) {
157  return names[idx];
158  }
159 
160  static constexpr std::size_t size( ) {
161  return N;
162  }
163 
164  template<std::size_t start_pos>
165  [[nodiscard]] constexpr std::size_t
166  find_name( daw::string_view key ) const {
167  UInt32 const hash = name_hash( key );
168 #if defined( _MSC_VER ) and not defined( __clang__ )
169  // MSVC has a bug where the list initialization isn't sequenced in order
170  // of appearance.
171  for( std::size_t n = 0; n < N; ++n ) {
172 #else
173  for( std::size_t n = start_pos; n < N; ++n ) {
174 #endif
175  if( names[n].hash_value == hash ) {
176  if constexpr( has_collisons ) {
177  if( DAW_JSON_UNLIKELY( key != *names[n].name ) ) {
178  continue;
179  }
180  }
181  return n;
182  }
183  }
184  return N;
185  }
186  };
187 
188  template<typename... MemberNames>
189  constexpr bool do_hashes_collide( ) {
190  std::array<UInt32, sizeof...( MemberNames )> hashes{
191  name_hash( MemberNames::name )... };
192 
193  daw::sort( hashes.data( ), hashes.data( ) + hashes.size( ) );
194  return daw::algorithm::adjacent_find(
195  hashes.begin( ), hashes.end( ),
196  []( UInt32 l, UInt32 r ) { return l == r; } ) != hashes.end( );
197  }
198 
199  template<typename Range, typename... JsonMembers>
200  constexpr auto make_locations_info( ) {
201  constexpr bool hashes_collide =
202  Range::force_name_equal_check or do_hashes_collide<JsonMembers...>( );
203  return locations_info_t<sizeof...( JsonMembers ), Range, hashes_collide>{
204  location_info_t<hashes_collide, Range>( &JsonMembers::name )... };
205  }
206 
207  /***
208  * Get the position from already seen JSON members or move the parser
209  * forward until we reach the end of the class or the member.
210  * @tparam pos JsonMember's position in locations
211  * @tparam JsonMember current member in json_class
212  * @tparam N Number of members in json_class
213  * @tparam Range see IteratorRange
214  * @param locations members location and names
215  * @param rng Current JSON data
216  * @return IteratorRange with begin( ) being start of value
217  */
218  template<std::size_t pos, typename JsonMember, std::size_t N, typename Range,
219  bool B>
220  [[nodiscard]] inline constexpr Range
221  find_class_member( locations_info_t<N, Range, B> &locations, Range &rng ) {
222 
223  daw_json_assert_weak( is_json_nullable_v<JsonMember> or
224  ( not locations[pos].missing( ) ) or
225  ( not rng.is_closing_brace_checked( ) ),
226  missing_member( JsonMember::name ), rng );
227 
228  rng.trim_left_unchecked( );
229  // TODO: should we check for end
230  while( locations[pos].missing( ) & ( rng.front( ) != '}' ) ) {
231  daw_json_assert_weak( rng.has_more( ), ErrorReason::UnexpectedEndOfData,
232  rng );
233  // TODO: fully unescape name
234  auto const name = parse_name( rng );
235  auto const name_pos = locations.template find_name<pos>( name );
236  if( name_pos >= locations.size( ) ) {
237  // This is not a member we are concerned with
238  (void)skip_value( rng );
239  rng.clean_tail( );
240  continue;
241  }
242  if( name_pos == pos ) {
243  locations[pos].location = Range::without_allocator( rng );
244  break;
245  } else {
246  // We are out of order, store position for later
247  // OLDTODO: use type knowledge to speed up skip
248  // OLDTODO: on skipped classes see if way to store
249  // member positions so that we don't have to
250  // reparse them after
251  // RESULT: storing preparsed is slower, don't try 3 times
252  // it also limits the type of things we can parse potentially
253  // Using locations to switch on BaseType is slower too
254  locations[name_pos].location = skip_value( rng ).without_allocator( );
255 
256  rng.clean_tail( );
257  }
258  }
259  return locations[pos].location.with_allocator( rng );
260  }
261 
262  namespace pocm_details {
263  /***
264  * Maybe skip json members
265  * @tparam Range see IteratorRange
266  * @param rng JSON data
267  * @param current_position current member index
268  * @param desired_position desired member index
269  */
270  template<typename Range>
271  constexpr void maybe_skip_members( Range &rng,
272  std::size_t &current_position,
273  std::size_t desired_position ) {
274 
275  rng.clean_tail( );
276  daw_json_assert_weak( rng.has_more( ), ErrorReason::UnexpectedEndOfData,
277  rng );
278  daw_json_assert_weak( current_position <= desired_position,
279  ErrorReason::OutOfOrderOrderedMembers, rng );
280  while( current_position < desired_position and rng.front( ) != ']' ) {
281  (void)skip_value( rng );
282  rng.clean_tail( );
283  ++current_position;
284  daw_json_assert_weak( rng.has_more( ), ErrorReason::UnexpectedEndOfData,
285  rng );
286  }
287  }
288  } // namespace pocm_details
289 
290  /***
291  * Parse a class member in an order json class(class as array)
292  * @tparam JsonMember type description of member to parse
293  * @tparam Range see IteratorRange
294  * @param member_position current position in array
295  * @param rng JSON data
296  * @return A reified value of type JsonMember::parse_to_t
297  */
298  template<typename JsonMember, typename Range>
299  [[nodiscard]] DAW_ATTRIBUTE_FLATTEN inline constexpr auto
300  parse_ordered_class_member( std::size_t &member_position, Range &rng ) {
301 
302  /***
303  * Some members specify their index so there may be gaps between member
304  * data elements in the array.
305  */
306  if constexpr( is_an_ordered_member_v<JsonMember> ) {
307  pocm_details::maybe_skip_members( rng, member_position,
308  JsonMember::member_index );
309  } else {
310  rng.clean_tail( );
311  }
312  using json_member_type = ordered_member_subtype_t<JsonMember>;
313 
314  // this is an out value, get position ready
315  ++member_position;
316  if( DAW_JSON_UNLIKELY( rng.front( ) == ']' ) ) {
317  if constexpr( is_json_nullable_v<ordered_member_subtype_t<JsonMember>> ) {
318  using constructor_t = typename json_member_type::constructor_t;
319  return constructor_t{ }( );
320  } else if constexpr( is_json_nullable_v<json_member_type> ) {
321  daw_json_error( missing_member( "ordered_class_member" ), rng );
322  }
323  }
324  return parse_value<json_member_type>(
325  ParseTag<json_member_type::expected_type>{ }, rng );
326  }
327 
328  /***
329  * Parse a member from a json_class
330  * @tparam member_position position in json_class member list
331  * @tparam JsonMember type description of member to parse
332  * @tparam N Number of members in json_class
333  * @tparam Range see IteratorRange
334  * @param locations location info for members
335  * @param rng JSON data
336  * @return parsed value from JSON data
337  */
338  template<std::size_t member_position, typename JsonMember, std::size_t N,
339  typename Range, bool B>
340  [[nodiscard]] DAW_ATTRIBUTE_FLATTEN inline constexpr json_result<JsonMember>
341  parse_class_member( locations_info_t<N, Range, B> &locations, Range &rng ) {
342  rng.clean_tail( );
343  static_assert( not is_no_name<JsonMember>,
344  "Array processing should never call parse_class_member" );
345 
346  daw_json_assert_weak( rng.is_at_next_class_member( ),
347  ErrorReason::MissingMemberNameOrEndOfClass, rng );
348  Range loc = [&] {
349  if constexpr( Range::has_allocator ) {
350  return find_class_member<member_position, JsonMember>( locations, rng )
351  .with_allocator( rng.get_allocator( ) );
352  } else {
353  return find_class_member<member_position, JsonMember>( locations, rng );
354  }
355  }( );
356 
357  // If the member was found loc will have it's position
358  if( loc.first == rng.first ) {
359  return parse_value<JsonMember>( ParseTag<JsonMember::expected_type>{ },
360  rng );
361  }
362  if( not loc.is_null( ) ) {
363  return parse_value<JsonMember, true>(
364  ParseTag<JsonMember::expected_type>{ }, loc );
365  }
366  // We cannot find the member, check if the member is nullable
367  if constexpr( is_json_nullable_v<JsonMember> ) {
368  return parse_value<JsonMember, true>(
369  ParseTag<JsonMember::expected_type>{ }, loc );
370  } else {
371  daw_json_error( missing_member( std::string_view(
372  JsonMember::name.data( ), JsonMember::name.size( ) ) ),
373  rng );
374  }
375  }
376 
377  template<typename Range>
378  DAW_ATTRIBUTE_FLATTEN inline constexpr void class_cleanup_now( Range &rng ) {
379  if( not rng.has_more( ) ) {
380  return;
381  }
382  rng.clean_tail( );
383  // If we fulfill the contract before all values are parses
384  rng.move_to_next_class_member( );
385  (void)rng.skip_class( );
386  // Yes this must be checked. We maybe at the end of document. After the
387  // 2nd try, give up
388  rng.trim_left_checked( );
389  }
390 
391  /***
392  * Parse to the user supplied class. The parser will run left->right if it
393  * can when the JSON document's order matches that of the order of the
394  * supplied classes ctor. If there is an order mismatch, store the
395  * start/finish of JSON members we are interested in and return that to the
396  * members parser when needed.
397  */
398  template<typename JsonClass, typename... JsonMembers, std::size_t... Is,
399  typename Range>
400  [[nodiscard]] inline constexpr JsonClass
401  parse_json_class( Range &rng, std::index_sequence<Is...> ) {
402  static_assert( has_json_data_contract_trait_v<JsonClass>,
403  "Unexpected type" );
404  rng.trim_left( );
405  // TODO, use member name
406  daw_json_assert_weak( rng.is_opening_brace_checked( ),
407  ErrorReason::InvalidClassStart, rng );
408  rng.set_class_position( );
409  rng.remove_prefix( );
410  rng.trim_left( );
411 
412  if constexpr( sizeof...( JsonMembers ) == 0 ) {
413  // Clang-CL with MSVC has issues if we don't do empties this way
414  class_cleanup_now( rng );
415  return construct_value<JsonClass>( json_class_constructor<JsonClass>,
416  rng );
417  } else {
418 
419 #if not defined( _MSC_VER ) or defined( __clang__ )
420  // Prior to C++20, this will guarantee the data structure is initialized
421  // at compile time. In the future, constinit should be fine.
422  constexpr auto known_locations_v =
423  make_locations_info<Range, JsonMembers...>( );
424 
425  auto known_locations = known_locations_v;
426 #else
427  // MSVC is doing the murmur3 hash at compile time incorrectly
428  // this puts it at runtime.
429  auto known_locations = make_locations_info<Range, JsonMembers...>( );
430 
431 #endif
432  if constexpr( is_guaranteed_rvo_v<Range> ) {
433  struct cleanup_t {
434  Range *rng_ptr;
435  CPP20CONSTEXPR inline ~cleanup_t( ) noexcept( false ) {
436 #ifdef HAS_CPP20CONSTEXPR
437  if( std::is_constant_evaluated( ) ) {
438 #endif
439  if( std::uncaught_exceptions( ) == 0 ) {
440  class_cleanup_now( *rng_ptr );
441  }
442 #ifdef HAS_CPP20CONSTEXPR
443  } else {
444  class_cleanup_now( *rng_ptr );
445  }
446 #endif
447  }
448  } const run_after_parse{ &rng };
449  (void)run_after_parse;
450  /*
451  * Rather than call directly use apply/tuple to evaluate left->right
452  */
454  JsonClass>::value ) {
455  return JsonClass{
456  parse_class_member<Is, traits::nth_type<Is, JsonMembers...>>(
457  known_locations, rng )... };
458  } else {
459 
460  using tp_t = decltype( std::forward_as_tuple(
461  parse_class_member<Is, traits::nth_type<Is, JsonMembers...>>(
462  known_locations, rng )... ) );
463 
464  return std::apply(
465  json_class_constructor<JsonClass>,
466  tp_t{ parse_class_member<Is, traits::nth_type<Is, JsonMembers...>>(
467  known_locations, rng )... } );
468  }
469  } else {
470  using tp_t = decltype( std::forward_as_tuple(
471  parse_class_member<Is, traits::nth_type<Is, JsonMembers...>>(
472  known_locations, rng )... ) );
473  JsonClass result = std::apply(
474  json_class_constructor<JsonClass>,
475  tp_t{ parse_class_member<Is, traits::nth_type<Is, JsonMembers...>>(
476  known_locations, rng )... } );
477  class_cleanup_now( rng );
478  return result;
479  }
480  }
481  }
482 
483  /***
484  * Parse to a class where the members are constructed from the values of a
485  * JSON array. Often this is used for geometric types like Point
486  */
487  template<typename JsonClass, typename... JsonMembers, typename Range>
488  [[nodiscard]] inline constexpr JsonClass
489  parse_ordered_json_class( Range &rng ) {
490  static_assert( has_json_data_contract_trait_v<JsonClass>,
491  "Unexpected type" );
492  static_assert(
493  std::is_invocable_v<json_class_constructor_t<JsonClass>,
494  typename JsonMembers::parse_to_t...>,
495  "Supplied types cannot be used for construction of this type" );
496 
497  rng.trim_left( );
498  daw_json_assert_weak( rng.is_opening_bracket_checked( ),
499  ErrorReason::InvalidArrayStart, rng );
500  rng.set_class_position( );
501  rng.remove_prefix( );
502  rng.trim_left( );
503 
504  size_t current_idx = 0;
505 
506  using tp_t = decltype( std::forward_as_tuple(
507  parse_ordered_class_member<JsonMembers>( current_idx, rng )... ) );
508 
509  if constexpr( is_guaranteed_rvo_v<Range> ) {
510  struct cleanup_t {
511  Range *ptr;
512  CPP20CONSTEXPR inline ~cleanup_t( ) noexcept( false ) {
513 #ifdef HAS_CPP20CONSTEXPR
514  if( std::is_constant_evaluated( ) ) {
515 #endif
516  if( std::uncaught_exceptions( ) == 0 ) {
517  (void)ptr->skip_array( );
518  }
519 #ifdef HAS_CPP20CONSTEXPR
520  } else {
521  (void)ptr->skip_array( );
522  }
523 #endif
524  }
525  } const run_after_parse{ &rng };
526  (void)run_after_parse;
527  return std::apply( json_class_constructor<JsonClass>,
528  tp_t{ parse_ordered_class_member<JsonMembers>(
529  current_idx, rng )... } );
530  } else {
531  JsonClass result =
532  std::apply( json_class_constructor<JsonClass>,
533  tp_t{ parse_ordered_class_member<JsonMembers>( current_idx,
534  rng )... } );
535 
536  (void)rng.skip_array( );
537  return result;
538  }
539  } // namespace daw::json::json_details
540 } // namespace daw::json::json_details
daw_json_serialize_impl.h
daw::json
Definition: daw_json_event_parser.h:17
daw_json_assert_weak
#define daw_json_assert_weak(Bool,...)
Definition: daw_json_assert.h:206
daw::json::construct_from_iso8601_timestamp
Definition: daw_json_link_impl.h:44
DAW_JSON_UNLIKELY
#define DAW_JSON_UNLIKELY(Bool)
Definition: daw_json_assert.h:35
daw_json_parse_value.h
daw_json_error
static DAW_JSON_NOINLINE void daw_json_error(daw::json::ErrorReason reason)
Definition: daw_json_assert.h:59
daw::name_hash
constexpr UInt32 name_hash(StringView key, std::uint32_t seed=0)
Definition: daw_murmur3.h:43
daw::json::construct_from_iso8601_timestamp< JsonNullable::Nullable >::result_type
std::optional< std::chrono::time_point< std::chrono::system_clock, std::chrono::milliseconds > > result_type
Definition: daw_json_link_impl.h:61
daw_json_arrow_proxy.h
daw::json::construct_from_iso8601_timestamp< JsonNullable::Never >::result_type
std::chrono::time_point< std::chrono::system_clock, std::chrono::milliseconds > result_type
Definition: daw_json_link_impl.h:49
daw_json_parse_common.h
daw::json::custom_from_converter_t
Definition: daw_json_link_impl.h:75
daw::json::JsonNullable::Never
@ Never
daw_murmur3.h
daw
Definition: daw_json_event_parser.h:17
daw::json::datetime::parse_iso8601_timestamp
constexpr std::chrono::time_point< std::chrono::system_clock, std::chrono::milliseconds > parse_iso8601_timestamp(daw::basic_string_view< char, Bounds, Ex > ts)
Definition: daw_json_parse_iso8601_utils.h:208
daw_json_assert.h
daw::json::JsonNullable
JsonNullable
Definition: daw_json_parse_common.h:409
daw_json_to_string.h
daw::json::force_aggregate_construction
Definition: daw_json_traits.h:50
daw_json_parse_iso8601_utils.h
CPP20CONSTEXPR
#define CPP20CONSTEXPR
Definition: daw_json_parse_common.h:31