fromJson static method

Future<GingaConfig> fromJson(
  1. String jsonOrSrc, [
  2. String? baseDirSrc,
  3. GingaCC? gingacc
])

Implementation

static Future<GingaConfig> fromJson(
  String jsonOrSrc, [
  String? baseDirSrc,
  GingaCC? gingacc,
]) async {
  gingacc ??= GingaCC();
  var trimmed = jsonOrSrc.trim();
  if (trimmed.isEmpty) {
    return GingaConfig();
  }
  if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
      (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
    final unquoted = trimmed.substring(1, trimmed.length - 1).trim();
    if (unquoted.startsWith('{') || unquoted.startsWith('[')) {
      trimmed = unquoted;
    }
  }
  String jsonString;
  if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
    jsonString = trimmed;
  } else {
    final uri = gingacc.resolveUri(trimmed, baseDirSrc);
    final loaded = (await gingacc.loadContent(uri)) ??
        (await gingacc.loadContent(trimmed));
    if (loaded == null) {
      throw FormatException('Failed to load GingaConfig from $jsonOrSrc');
    }
    jsonString = loaded.trim();
  }
  final dynamic decoded;
  try {
    decoded = json5Decode(jsonString);
  } catch (e) {
    if (e is FormatException) rethrow;
    throw FormatException(e.toString());
  }
  if (decoded is! Map) {
    throw const FormatException('Expected a JSON object for GingaConfig');
  }

  final Map<String, String> envVars = {};

  void extractFromMap(Map map, [String prefix = '']) {
    for (final entry in map.entries) {
      final key = entry.key.toString();
      final value = entry.value;
      final fullKey = prefix.isEmpty ? key : '$prefix$key';
      if (value is Map) {
        extractFromMap(value, '$fullKey.');
      } else if (value != null) {
        envVars[fullKey] = value.toString();
      }
    }
  }

  final rawEnv = decoded['envVariables'];
  if (rawEnv is Map) {
    extractFromMap(rawEnv);
  }

  for (final group in supportedGroups) {
    final groupName = group.substring(0, group.length - 1);
    final val = decoded[groupName] ?? decoded[group];
    if (val is Map) {
      extractFromMap(val, group);
    }
  }

  for (final entry in decoded.entries) {
    final key = entry.key.toString();
    for (final group in supportedGroups) {
      if (key.startsWith(group) &&
          entry.value != null &&
          entry.value is! Map) {
        envVars[key] = entry.value.toString();
      }
    }
  }

  final users = Users();
  final usersObj = decoded['usersDataJson'] ?? decoded['userDataJson'];
  if (usersObj is String) {
    final trimmedUsers = usersObj.trim();
    if (trimmedUsers.startsWith('{') || trimmedUsers.startsWith('[')) {
      users.loadUserData(trimmedUsers);
    } else {
      final base = baseDirSrc ?? (trimmed.startsWith('{') ? null : trimmed);
      final uri = gingacc.resolveUri(trimmedUsers, base);
      final loaded = (await gingacc.loadContent(uri)) ??
          (await gingacc.loadContent(trimmedUsers));
      if (loaded != null && loaded.isNotEmpty) {
        users.loadUserData(loaded);
      }
    }
  } else if (usersObj is List) {
    users.importUsers(usersObj);
  } else if (usersObj is Map<String, dynamic>) {
    users.loadUserData(jsonEncode(usersObj));
  } else if (usersObj is Map) {
    users.loadUserData(jsonEncode(Map<String, dynamic>.from(usersObj)));
  }

  const allowedKeys = {
    'appSrc',
    'mainAvSrc',
    'startWithCCWS',
    'startWithMainAv',
    'graphsPlaneBounds',
    'graphsPlaneWidth',
    'graphsPlaneHeight',
    'envVariables',
    'usersDataJson',
    'userDataJson',
  };

  for (final key in decoded.keys) {
    final keyStr = key.toString();
    if (allowedKeys.contains(keyStr)) continue;
    final isGroup = supportedGroups.any((g) =>
        keyStr == g ||
        keyStr == g.substring(0, g.length - 1) ||
        keyStr.startsWith(g));
    if (!isGroup) {
      throw FormatException("Format error: bad key '$keyStr'");
    }
  }

  Rectangle<double> planeBounds =
      const Rectangle<double>(0.0, 0.0, 720.0, 480.0);
  final rawBounds = decoded['graphsPlaneBounds'];
  if (rawBounds is Map) {
    planeBounds = Rectangle<double>(
      (rawBounds['left'] as num?)?.toDouble() ?? 0.0,
      (rawBounds['top'] as num?)?.toDouble() ?? 0.0,
      (rawBounds['width'] as num?)?.toDouble() ?? 720.0,
      (rawBounds['height'] as num?)?.toDouble() ?? 480.0,
    );
  } else if (rawBounds is List && rawBounds.length >= 4) {
    planeBounds = Rectangle<double>(
      (rawBounds[0] as num).toDouble(),
      (rawBounds[1] as num).toDouble(),
      (rawBounds[2] as num).toDouble(),
      (rawBounds[3] as num).toDouble(),
    );
  } else if (decoded.containsKey('graphsPlaneWidth') ||
      decoded.containsKey('graphsPlaneHeight')) {
    final rawW = decoded['graphsPlaneWidth'];
    final w = rawW is num
        ? rawW.toDouble()
        : (rawW != null ? double.tryParse(rawW.toString()) ?? 720.0 : 720.0);
    final rawH = decoded['graphsPlaneHeight'];
    final h = rawH is num
        ? rawH.toDouble()
        : (rawH != null ? double.tryParse(rawH.toString()) ?? 480.0 : 480.0);
    planeBounds = Rectangle<double>(0.0, 0.0, w, h);
  }

  return GingaConfig(
    appSrc: decoded['appSrc'] as String?,
    mainAvSrc: decoded['mainAvSrc'] as String? ?? defaultMainAvSrc,
    startWithCCWS: decoded['startWithCCWS'] as bool? ?? false,
    startWithMainAv: decoded['startWithMainAv'] as bool? ?? false,
    graphsPlaneBounds: planeBounds,
    envVariables: envVars,
    users: users,
  );
}