com.amanotes.gdk 0.2.64 → 0.2.65-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/Editor/Extra/SDKVersionTracking.AirTable.cs +73 -0
  3. package/Editor/Extra/SDKVersionTracking.AirTable.cs.meta +3 -0
  4. package/Editor/Extra/SDKVersionTracking.cs +232 -0
  5. package/Editor/Extra/SDKVersionTracking.cs.meta +11 -0
  6. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  7. package/Extra/CheckDiskSpace.unitypackage +0 -0
  8. package/Extra/ForceUpdate.unitypackage +0 -0
  9. package/Extra/GoogleCMP.unitypackage +0 -0
  10. package/Extra/GoogleCMP.unitypackage.meta +7 -0
  11. package/Extra/PostProcessor.unitypackage +0 -0
  12. package/Extra/SDKVersionTracking.unitypackage +0 -0
  13. package/Packages/AmaGDKConfig.unitypackage +0 -0
  14. package/Packages/AmaGDKExample.unitypackage +0 -0
  15. package/Packages/AmaGDKTest.unitypackage +0 -0
  16. package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
  17. package/Packages/AppsFlyerAdapter.unitypackage +0 -0
  18. package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
  19. package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
  20. package/Packages/IronSourceAdapter.unitypackage +0 -0
  21. package/Packages/RevenueCatAdapter.unitypackage +0 -0
  22. package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
  23. package/Runtime/AmaGDK.Adapters.cs +1 -1
  24. package/Runtime/AmaGDK.Ads.cs +29 -2
  25. package/Runtime/AmaGDK.Analytics.cs +2 -2
  26. package/Runtime/AmaGDK.Mono.cs +111 -0
  27. package/Runtime/AmaGDK.Mono.cs.meta +3 -0
  28. package/Runtime/AmaGDK.RemoteConfig.cs +3 -4
  29. package/Runtime/AmaGDK.Singleton.cs +27 -0
  30. package/Runtime/AmaGDK.Singleton.cs.meta +3 -0
  31. package/Runtime/AmaGDK.UserProfile.cs +1 -2
  32. package/Runtime/AmaGDK.cs +10 -54
  33. package/Runtime/Core/GDKRoutine.cs +61 -0
  34. package/Runtime/Core/GDKRoutine.cs.meta +3 -0
  35. package/Runtime/Core/GDKUnityCallback.cs +141 -0
  36. package/Runtime/Core/GDKUnityCallback.cs.meta +3 -0
  37. package/Runtime/Core.meta +8 -0
  38. package/Runtime/Internal/AmaGDK.Internal.cs +5 -9
  39. package/Runtime/Internal/AmaGDK.Utils.cs +63 -80
  40. package/Runtime/Internal/ForceQuitMonitor.cs +111 -0
  41. package/Runtime/Internal/ForceQuitMonitor.cs.meta +3 -0
  42. package/Runtime/Klavar/Attributes/AccumulatedCountAttribute.cs +0 -1
  43. package/Runtime/Klavar/Attributes/MinMaxAttribute.cs +86 -79
  44. package/Runtime/Klavar/Attributes/MinMaxLengthAttribute.cs +47 -26
  45. package/Runtime/Klavar/Attributes/NotNullAttribute.cs +1 -1
  46. package/Runtime/Klavar/Attributes/RegexPatternAttribute.cs +10 -7
  47. package/Runtime/Klavar/Attributes/ToStringAttribute.cs +1 -1
  48. package/Runtime/Klavar/KlavarContainer.cs +21 -5
  49. package/Runtime/Klavar/KlavarEvent.cs +4 -1
  50. package/Runtime/Utils/GDKUtils.cs +35 -0
  51. package/Runtime/Utils/GDKUtils.cs.meta +3 -0
  52. package/Runtime/Utils.meta +8 -0
  53. package/package.json +1 -1
package/Runtime/AmaGDK.cs CHANGED
@@ -17,17 +17,18 @@ namespace Amanotes.Core
17
17
  {
18
18
  public partial class AmaGDK : MonoBehaviour
19
19
  {
20
- public const string VERSION = "0.2.64";
21
-
22
- internal static AmaGDK _instance;
23
- internal static bool _allowInit = false;
20
+ public const string VERSION = "0.2.65-2";
21
+
24
22
  internal static Status _status = Status.None;
25
23
  private static ConfigAsset _config = null;
26
24
  internal static readonly GDKGeoLocation GeoLocation = new GDKGeoLocation();
27
25
  internal static readonly GDKServerTime ServerTime = new GDKServerTime();
26
+ private static readonly ForceQuitMonitor ForceQuit = new ForceQuitMonitor();
28
27
 
28
+ internal static bool _allowInit = false;
29
29
 
30
30
  public bool autoInit = true;
31
+
31
32
  internal static partial class Event
32
33
  {
33
34
  public const string GDK_HOOK_START = nameof(GDK_HOOK_START);
@@ -48,26 +49,6 @@ namespace Amanotes.Core
48
49
  return _config;
49
50
  }
50
51
  }
51
-
52
- private void Awake()
53
- {
54
- if (_instance != null)
55
- {
56
- if (_instance != this)
57
- {
58
- LogWarning(MULTIPLE_INSTANCE);
59
- Destroy(this);
60
- }
61
- LogWarning("Should never be here!");
62
- return;
63
- }
64
-
65
- _instance = this;
66
- DontDestroyOnLoad(this);
67
-
68
- _allowInit = _allowInit || autoInit;
69
- StartCoroutine(InitRoutine());
70
- }
71
52
 
72
53
  IEnumerator InitHook()
73
54
  {
@@ -174,6 +155,8 @@ namespace Amanotes.Core
174
155
 
175
156
  IEnumerator InitRoutine()
176
157
  {
158
+ _allowInit = _allowInit || autoInit;
159
+
177
160
  if (Config == null)
178
161
  {
179
162
  throw new Exception("[AmaGDK] " + CONFIG_NOT_FOUND);
@@ -205,6 +188,8 @@ namespace Amanotes.Core
205
188
  ServerTime.UpdateTime();
206
189
  }
207
190
 
191
+ ForceQuit.Init();
192
+
208
193
  _status = Status.Ready;
209
194
  dispatcher.Dispatch(Event.GDK_READY);
210
195
 
@@ -357,35 +342,6 @@ namespace Amanotes.Core
357
342
  public const string REVENUECAT = "RevenueCat";
358
343
  }
359
344
  }
360
-
361
- public partial class AmaGDK
362
- {
363
- internal static event Action onFrameUpdate;
364
- internal static event Action<bool> onApplicationPause;
365
- internal static event Action<bool> applicationFocus;
366
- internal static event Action applicationQuit;
367
-
368
- private void OnApplicationPause(bool pauseStatus)
369
- {
370
- onApplicationPause?.Invoke(pauseStatus);
371
- }
372
-
373
- private void OnApplicationFocus(bool hasFocus)
374
- {
375
- applicationFocus?.Invoke(hasFocus);
376
- }
377
-
378
- private void Update()
379
- {
380
- if (!isReady) return;
381
- onFrameUpdate?.Invoke();
382
- }
383
-
384
- private void OnApplicationQuit()
385
- {
386
- applicationQuit?.Invoke();
387
- }
388
- }
389
345
 
390
346
  public partial class AmaGDK // Switch dev mode
391
347
  {
@@ -499,7 +455,7 @@ namespace Amanotes.Core
499
455
  {
500
456
  if (completed)
501
457
  {
502
- LogWarning("Register happen too late: Hook completed!");
458
+ LogWarning("Add happen too late: Hook completed!");
503
459
  return;
504
460
  }
505
461
 
@@ -0,0 +1,61 @@
1
+ using System;
2
+ using System.Collections;
3
+ using UnityEngine;
4
+ namespace Amanotes.Core.Internal
5
+ {
6
+ [Flags]
7
+ internal enum RoutineStatus
8
+ {
9
+ None,
10
+ Started = 1,
11
+ Stopped = 2
12
+ }
13
+
14
+ public class GDKRoutine
15
+ {
16
+ private RoutineStatus status;
17
+ private readonly IEnumerator action;
18
+ private Coroutine routine;
19
+
20
+ public bool isAlive => (status & RoutineStatus.Stopped) == 0;
21
+
22
+ public GDKRoutine(IEnumerator action)
23
+ {
24
+ this.action = action;
25
+ status = RoutineStatus.None;
26
+ }
27
+
28
+ public void StartRoutine()
29
+ {
30
+ if (!isAlive)
31
+ {
32
+ // StopRoutine() called before start: do nothing
33
+ return;
34
+ }
35
+
36
+ status |= RoutineStatus.Started;
37
+ routine = AmaGDK._instance.StartCoroutine(action);
38
+ }
39
+
40
+ public void StopRoutine()
41
+ {
42
+ status |= RoutineStatus.Stopped;
43
+ if (routine != null) AmaGDK._instance.StopCoroutine(routine);
44
+ }
45
+ }
46
+
47
+ public static partial class GDKUtils
48
+ {
49
+ public static GDKRoutine StartCoroutine(IEnumerator action)
50
+ {
51
+ var routine = new GDKRoutine(action);
52
+ DoOnMainThread(routine.StartRoutine);
53
+ return routine;
54
+ }
55
+
56
+ public static void StopCoroutine(GDKRoutine gdkRoutine)
57
+ {
58
+ gdkRoutine.StopRoutine();
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 1a341fd3b1ed4dff93eaccd1b4d442a3
3
+ timeCreated: 1718160748
@@ -0,0 +1,141 @@
1
+ using System;
2
+
3
+ namespace Amanotes.Core.Internal
4
+ {
5
+ public interface IOnApplicationPause { void OnApplicationPause(bool pauseStatus); }
6
+ public interface IOnApplicationQuit { void OnApplicationQuit(); }
7
+ public interface IOnApplicationFocus { void OnApplicationFocus(bool hasFocus); }
8
+ public interface IOnFrameUpdate { void OnFrameUpdate(); }
9
+ }
10
+
11
+ namespace Amanotes.Core.Internal
12
+ {
13
+ public class GDKUnityCallback
14
+ {
15
+ public Action<bool> OnApplicationPause;
16
+ public Action<bool> OnApplicationFocus;
17
+ public Action OnApplicationQuit;
18
+ public Action OnFrameUpdate;
19
+ public Action DoOnceNextFrame;
20
+
21
+ // Lock object to ensure thread-safe operations
22
+ private readonly object _locker = new object();
23
+
24
+ // Add Listener
25
+ public void AddCallback(ref Action source, Action callback)
26
+ {
27
+ if (callback == null) return;
28
+ lock (_locker)
29
+ {
30
+ source -= callback;
31
+ source += callback;
32
+ }
33
+ }
34
+ public void AddCallback<T>(ref Action<T> source, Action<T> callback)
35
+ {
36
+ if (callback == null) return;
37
+ lock (_locker)
38
+ {
39
+ source -= callback;
40
+ source += callback;
41
+ }
42
+ }
43
+
44
+
45
+ // Remove Listener
46
+ public void RemoveCallback(ref Action source, Action callback)
47
+ {
48
+ if (callback == null) return;
49
+ lock (_locker)
50
+ {
51
+ source -= callback;
52
+ }
53
+ }
54
+ public void RemoveCallback<T>(ref Action<T> source, Action<T> callback)
55
+ {
56
+ if (callback == null) return;
57
+ lock (_locker)
58
+ {
59
+ source -= callback;
60
+ }
61
+ }
62
+
63
+
64
+ // Invoke
65
+ public void Invoke(ref Action source)
66
+ {
67
+ if (source == null) return;
68
+ Action actionsToInvoke;
69
+ lock (_locker)
70
+ {
71
+ bool remove = source == DoOnceNextFrame;
72
+ actionsToInvoke = source;
73
+ if (remove) source = null;
74
+ }
75
+
76
+ GDKUtils.SafeInvoke(actionsToInvoke);
77
+ }
78
+ public void Invoke<T>(ref Action<T> source, T data)
79
+ {
80
+ if (source == null) return;
81
+ Action<T> actionsToInvoke;
82
+ lock (_locker)
83
+ {
84
+ actionsToInvoke = source;
85
+ }
86
+
87
+ GDKUtils.SafeInvoke(actionsToInvoke, data);
88
+ }
89
+
90
+
91
+ // Observer
92
+ public void AddObserver<T>(T observer)
93
+ {
94
+ if (observer == null) return;
95
+
96
+ if (observer is IOnFrameUpdate u)
97
+ {
98
+ AddCallback(ref OnFrameUpdate, u.OnFrameUpdate);
99
+ }
100
+
101
+ if (observer is IOnApplicationPause p)
102
+ {
103
+ AddCallback(ref OnApplicationPause, p.OnApplicationPause);
104
+ }
105
+
106
+ if (observer is IOnApplicationFocus f)
107
+ {
108
+ AddCallback(ref OnApplicationFocus, f.OnApplicationFocus);
109
+ }
110
+
111
+ if (observer is IOnApplicationQuit q)
112
+ {
113
+ AddCallback(ref OnApplicationQuit, q.OnApplicationQuit);
114
+ }
115
+ }
116
+ public void RemoveObserver<T>(T observer)
117
+ {
118
+ if (observer == null) return;
119
+
120
+ if (observer is IOnFrameUpdate u)
121
+ {
122
+ RemoveCallback(ref OnFrameUpdate, u.OnFrameUpdate);
123
+ }
124
+
125
+ if (observer is IOnApplicationPause p)
126
+ {
127
+ RemoveCallback(ref OnApplicationPause, p.OnApplicationPause);
128
+ }
129
+
130
+ if (observer is IOnApplicationFocus f)
131
+ {
132
+ RemoveCallback(ref OnApplicationFocus, f.OnApplicationFocus);
133
+ }
134
+
135
+ if (observer is IOnApplicationQuit q)
136
+ {
137
+ RemoveCallback(ref OnApplicationQuit, q.OnApplicationQuit);
138
+ }
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 597f549a7af24670b911da2047054e5e
3
+ timeCreated: 1718085791
@@ -0,0 +1,8 @@
1
+ fileFormatVersion: 2
2
+ guid: 2b84f1cdb15ec4442b3a2f0b038c711c
3
+ folderAsset: yes
4
+ DefaultImporter:
5
+ externalObjects: {}
6
+ userData:
7
+ assetBundleName:
8
+ assetBundleVariant:
@@ -87,12 +87,13 @@ namespace Amanotes.Core.Internal
87
87
  public static void LogError(string message)
88
88
  {
89
89
  if (Config.common.logLevel >= LogLevel.Error) Debug.LogError($"AmaGDK {message}");
90
- if (logNonfatalErrorHook != null)
90
+ if (logNonfatalErrorHook == null) return;
91
+ GDKUtils.DoOnMainThread(() =>
91
92
  {
92
93
  if (nonFatalDict.Contains(message)) return; // LogOnce
93
94
  nonFatalDict.Add(message);
94
- logNonfatalErrorHook(message);
95
- }
95
+ logNonfatalErrorHook(message);
96
+ });
96
97
  }
97
98
  public static void Log(string message)
98
99
  {
@@ -106,7 +107,7 @@ namespace Amanotes.Core.Internal
106
107
  private static bool presetEnableEventStat;
107
108
  private static LogLevel presetLogLevel = LogLevel.Warning;
108
109
 
109
- public static void PresetConfig()
110
+ public static void LoadPresetConfig()
110
111
  {
111
112
  presetLogLevel = Config.common.logLevel;
112
113
  Config.common.logLevel = LogLevel.Warning;
@@ -184,11 +185,6 @@ namespace Amanotes.Core.Internal
184
185
  }
185
186
  }
186
187
 
187
- public interface IOnApplicationPause { void OnApplicationPause(bool pauseStatus); }
188
- public interface IOnApplicationQuit { void OnApplicationQuit(); }
189
- public interface IOnApplicationFocus { void OnApplicationFocus(bool hasFocus); }
190
- public interface IOnFrameUpdate { void OnFrameUpdate(); }
191
-
192
188
  public static class Utils // Hidden Utils
193
189
  {
194
190
  private static readonly Dictionary<string, float> LastFireInSessions = new Dictionary<string, float>();
@@ -3,6 +3,7 @@ using System.Collections;
3
3
  using System.Collections.Concurrent;
4
4
  using System.Collections.Generic;
5
5
  using System.IO;
6
+ using System.Linq;
6
7
  using System.Reflection;
7
8
  using System.Text;
8
9
  using System.Text.RegularExpressions;
@@ -12,65 +13,8 @@ using static Amanotes.Core.Internal.Logging;
12
13
 
13
14
  namespace Amanotes.Core.Internal
14
15
  {
15
- public static class GDKUtils
16
+ public static partial class GDKUtils
16
17
  {
17
- public static void StartCoroutine(IEnumerator action)
18
- {
19
- AmaGDK._instance.StartCoroutine(action);
20
- }
21
- public static void DelayCall(float seconds, Action action)
22
- {
23
- AmaGDK._instance.StartCoroutine(DelayCallRoutine(seconds, action));
24
- }
25
-
26
- public static void RegisterUnityCallbacks<T>(T target)
27
- {
28
- if (target == null)
29
- {
30
- LogWarning("RegisterUnityCallbacks: Target is null!");
31
- return;
32
- }
33
-
34
- if (target is IOnFrameUpdate u)
35
- {
36
- AmaGDK.onFrameUpdate -= u.OnFrameUpdate;
37
- AmaGDK.onFrameUpdate += u.OnFrameUpdate;
38
- }
39
-
40
- if (target is IOnApplicationPause p)
41
- {
42
- AmaGDK.onApplicationPause -= p.OnApplicationPause;
43
- AmaGDK.onApplicationPause += p.OnApplicationPause;
44
- }
45
-
46
- if (target is IOnApplicationFocus f)
47
- {
48
- AmaGDK.applicationFocus -= f.OnApplicationFocus;
49
- AmaGDK.applicationFocus += f.OnApplicationFocus;
50
- }
51
-
52
- if (target is IOnApplicationQuit q)
53
- {
54
- AmaGDK.applicationQuit -= q.OnApplicationQuit;
55
- AmaGDK.applicationQuit += q.OnApplicationQuit;
56
- }
57
- }
58
-
59
- private static IEnumerator DelayCallRoutine(float seconds, Action callback)
60
- {
61
- yield return new WaitForSecondsRealtime(seconds);
62
- callback();
63
- }
64
-
65
- public static IEnumerator Timeout(float timeoutInSecs, Func<bool> completeCheckFunc)
66
- {
67
- float time = Time.realtimeSinceStartup;
68
- while (completeCheckFunc() == false)
69
- {
70
- yield return null;
71
- if (Time.realtimeSinceStartup - time > timeoutInSecs) yield break;
72
- }
73
- }
74
18
 
75
19
 
76
20
  public static bool IsFromTemplate(string formattedString, string template)
@@ -157,7 +101,7 @@ namespace Amanotes.Core.Internal
157
101
  segments[matches.Count] = template.Substring(lastIndex);
158
102
  return segments;
159
103
  }
160
-
104
+
161
105
  internal static void ThrowIf(bool condition, string message)
162
106
  {
163
107
  if (condition)
@@ -455,11 +399,7 @@ namespace Amanotes.Core.Internal
455
399
  {
456
400
  public static string TupleToJson(params (string key, object value)[] parameters)
457
401
  {
458
- var tupleToDict = new Dictionary<string, object>();
459
- foreach (var item in parameters)
460
- {
461
- tupleToDict.Add(item.key, item.value);
462
- }
402
+ var tupleToDict = parameters.ToDictionary(item => item.key, item => item.value);
463
403
  return DictionaryToJson(tupleToDict);
464
404
  }
465
405
 
@@ -476,13 +416,13 @@ namespace Amanotes.Core.Internal
476
416
  Profiler.BeginSample("AmaGDK.JsonUtils.DictionaryToJson()");
477
417
  var jsonSb = new StringBuilder();
478
418
  jsonSb.Append("{");
479
- var isFirstElement = true;
419
+ bool isFirstElement = true;
480
420
 
481
421
  foreach (DictionaryEntry entry in inputDict)
482
422
  {
483
423
  if (entry.Key == null || string.IsNullOrEmpty(entry.Key.ToString())) continue;
484
- var key = entry.Key.ToString();
485
- var value = entry.Value;
424
+ string key = entry.Key.ToString();
425
+ object value = entry.Value;
486
426
 
487
427
  if (isFirstElement)
488
428
  isFirstElement = false;
@@ -494,7 +434,7 @@ namespace Amanotes.Core.Internal
494
434
  }
495
435
 
496
436
  jsonSb.AppendFormat("{0}{1}", format ? "\n" : "", "}");
497
- var result = jsonSb.ToString();
437
+ string result = jsonSb.ToString();
498
438
  Profiler.EndSample();
499
439
 
500
440
  return result;
@@ -503,7 +443,7 @@ namespace Amanotes.Core.Internal
503
443
  private static void GetJsonIListValue(ref StringBuilder jsonSb, string key, IList list, bool format)
504
444
  {
505
445
  jsonSb.Append("[");
506
- var isFirstElement = true;
446
+ bool isFirstElement = true;
507
447
 
508
448
  foreach (object item in list)
509
449
  {
@@ -530,7 +470,7 @@ namespace Amanotes.Core.Internal
530
470
 
531
471
  if (value is string)
532
472
  {
533
- var s = value.ToString();
473
+ string s = value.ToString();
534
474
  if (checkJsonInString)
535
475
  {
536
476
  if (s.StartsWith("{") && s.EndsWith("}"))
@@ -554,34 +494,77 @@ namespace Amanotes.Core.Internal
554
494
  return;
555
495
  }
556
496
 
557
- if (value is bool)
497
+ if (value is bool b)
558
498
  {
559
- jsonSb.Append((bool)value ? "true" : "false");
499
+ jsonSb.Append(b ? "true" : "false");
560
500
  return;
561
501
  }
562
502
 
563
- if (value is IDictionary)
503
+ if (value is IDictionary dictionary)
564
504
  {
565
- jsonSb.Append($"{DictionaryToJson((IDictionary)value, format)}");
505
+ jsonSb.Append($"{DictionaryToJson(dictionary, format)}");
566
506
  return;
567
507
  }
568
508
 
569
- if (value is IList || value is Array)
509
+ if (value is IList list)
570
510
  {
571
- GetJsonIListValue(ref jsonSb, key, (IList)value, format);
511
+ GetJsonIListValue(ref jsonSb, key, list, format);
572
512
  return;
573
513
  }
574
514
 
575
515
  jsonSb.Append($"{JsonUtility.ToJson(value, format)}");
576
516
  }
577
517
 
578
- private static string EscapeJsonString(string input)
518
+ /// <summary>
519
+ /// Escapes a string for inclusion in a JSON document according to RFC 8259.
520
+ /// For more details, see <a href="https://www.rfc-editor.org/rfc/rfc8259.txt">RFC 8259</a>.
521
+ /// </summary>
522
+ /// <param name="input">The input string to be escaped.</param>
523
+ /// <returns>The escaped string.</returns>
524
+ public static string EscapeJsonString(string input)
579
525
  {
580
- //Match any occurrence of a backslash(\) and double quotation (")
581
- string pattern = "[\\\"]";
582
- return Regex.Replace(input, pattern, match => $"\\{match.Value}");
526
+ var sb = new StringBuilder();
527
+ foreach (char c in input)
528
+ {
529
+ switch (c)
530
+ {
531
+ case '\"': sb.Append("\\\""); break;
532
+ case '\\': sb.Append("\\\\"); break;
533
+ case '\b': sb.Append("\\b"); break;
534
+ case '\f': sb.Append("\\f"); break;
535
+ case '\n': sb.Append("\\n"); break;
536
+ case '\r': sb.Append("\\r"); break;
537
+ case '\t': sb.Append("\\t"); break;
538
+ default:
539
+ if (c < 0x20)
540
+ {
541
+ // Encode as \uXXXX for control characters
542
+ sb.Append("\\u" + ((int)c).ToString("X4"));
543
+ }
544
+ else if (char.IsSurrogate(c))
545
+ {
546
+ // Handle surrogate pairs
547
+ if (char.IsHighSurrogate(c))
548
+ {
549
+ sb.Append("\\u" + ((int)c).ToString("X4"));
550
+ }
551
+ else if (char.IsLowSurrogate(c))
552
+ {
553
+ sb.Append("\\u" + ((int)c).ToString("X4"));
554
+ }
555
+ }
556
+ else
557
+ {
558
+ // Keep characters as is if they are in the UTF-8 range
559
+ sb.Append(c);
560
+ }
561
+ break;
562
+ }
563
+ }
564
+ return sb.ToString();
583
565
  }
584
566
 
567
+
585
568
  //public static T[] FromJsonToArray<T>(string json)
586
569
  //{
587
570
  // if (!json.StartsWith("{\"items\":"))