babalae/better-genshin-impact · critical · Exception

tp.json deserialization failed

Error message

tp.json deserialization failed

What it means

Thrown as a generic Exception by the MapLazyAssets constructor when Newtonsoft.Json's JObject.Parse(json)["data"] resolves to null and ToObject returns null. This means tp.json either lacks a top-level 'data' key or its value is JSON null. The teleport/dungeon/Goddess position data for the entire map depends on this array, so the singleton cannot be constructed.

Source

Thrown at BetterGenshinImpact/GameTask/Common/Element/Assets/MapLazyAssets.cs:45

        { "璃月", [270, -666] },
        { "稻妻", [-4400, -3050] },
        { "须弥", [2877, -374] },
        { "枫丹", [4515, 3631] },
        { "纳塔", [8973.5, -1879.1] },
        { "挪德卡莱", [9542.25, 1661.84] },
        { "至冬", [6755.051, 9480.659] }
    };

    public IReadOnlyDictionary<string, GiTpPosition> DomainPositionMap => _domainPositionMap;
    public IReadOnlyDictionary<string, GiTpPosition> GoddessPositions => _goddessPositions;

    public IReadOnlyList<string> DomainNameList => _domainNameList;
    public IReadOnlyDictionary<string, List<GiTpPosition>> CountryToDomains => _countryToDomains;

    private MapLazyAssets()
    {
        var json = File.ReadAllText(Global.Absolute(@"GameTask\AutoTrackPath\Assets\tp.json"));
        var worldScenes = Newtonsoft.Json.Linq.JObject.Parse(json)["data"]?.ToObject<List<GiWorldScene>>() ?? throw new Exception("tp.json deserialization failed");
        ScenesDic = worldScenes.ToDictionary(x => x.MapName, x => x);


        // 取出秘境 description=Domain
        var teyvatTpPositions = ScenesDic[nameof(MapTypes.Teyvat)].Points;
        foreach (var tp in teyvatTpPositions.Where(tp => tp.Type == "BlessDomain" || tp.Type == "ForgeryDomain" || tp.Type == "MasteryDomain"))
        {
            _domainPositionMap[tp.Name!] = tp;
            _domainNameList.Add(tp.Name!);

            if (!string.IsNullOrEmpty(tp.Country))
            {
                if (!_countryToDomains.ContainsKey(tp.Country))
                {
                    _countryToDomains[tp.Country] = [];
                }

                _countryToDomains[tp.Country].Add(tp);

View on GitHub (pinned to a7cb36712d)

Solutions

  1. Restore GameTask/AutoTrackPath/Assets/tp.json from the repository — verify it has a 'data' array key.
  2. Validate the JSON with a linter to confirm the top-level structure is { "data": [ ... ] }.
  3. Check that the file was not truncated during git operations (verify file size).

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

var json = File.ReadAllText(path);
var parsed = Newtonsoft.Json.Linq.JObject.Parse(json);
if (parsed["data"] == null || parsed["data"]!.Type != Newtonsoft.Json.Linq.JTokenType.Array)
{
    // tp.json missing 'data' array — abort
}

Type guard

static bool IsValidTpJson(string json)
{
    var token = Newtonsoft.Json.Linq.JObject.Parse(json)["data"];
    return token != null && token.Type == Newtonsoft.Json.Linq.JTokenType.Array;
}

Try / catch

try { MapLazyAssets.Get(); }
catch (Exception ex) when (ex.Message.Contains("tp.json")) { /* restore tp.json from repo */ }

Prevention

When it happens

Trigger: tp.json at GameTask/AutoTrackPath/Assets/tp.json is missing the 'data' key, has "data": null, or is malformed JSON that JObject.Parse somehow accepted with a null data node.

Common situations: The tp.json file is corrupted, truncated, or from an incompatible version. A manual edit removed or renamed the 'data' field. The file was replaced with a different JSON structure. MapLazyAssets.Get() is called for the first time at startup, so this becomes a launch-time crash.

Related errors


AI-assisted analysis of babalae/better-genshin-impact@a7cb36712d (2026-08-13). Data as JSON: /api/errors/feed336219c3ac05. Report an issue: GitHub.