using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; namespace GitConverter.Lib.Converters { public static partial class ConverterUtils { /// /// Helper that detects the specific JSON GIS format for a given JSON payload or file. /// /// /// - Supported detections: /// * GeoJson : FeatureCollection objects (checks "type":"FeatureCollection") /// * EsriJson : Esri JSON feature set (checks presence of "features" and "spatialReference") /// * GeoJsonSeq : Newline-delimited/sequence GeoJSON (JSON array or NDJSON) /// * TopoJson : TopoJSON (checks "type":"Topology") /// - Methods provide both exception-throwing and non-throwing variants. /// - Detection is heuristic based on top-level JSON structure and keys; for ambiguous inputs consider /// additional content inspection or an explicit conversion option. /// - This helper depends on Newtonsoft.Json (Json.NET). For very large files consider streaming/parsing only the header. /// public static class JsonFormatDetector { /// /// Enumeration of detected JSON GIS formats. /// public enum Format { Unknown = 0, GeoJson, EsriJson, GeoJsonSeq, TopoJson } /// /// Detect format from a JSON file. Throws on IO or parse errors. /// public static Format DetectFromFile(string filePath) { if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentNullException(nameof(filePath)); var text = File.ReadAllText(filePath); return DetectFromString(text); } /// /// Non-throwing variant that attempts to detect format from a JSON file. /// Returns true when detection succeeded (result may be Unknown), false when file/read/parse failed. /// public static bool TryDetectFromFile(string filePath, out Format result) { result = Format.Unknown; try { if (string.IsNullOrWhiteSpace(filePath)) return false; if (!File.Exists(filePath)) return false; var text = File.ReadAllText(filePath); result = DetectFromString(text); return true; } catch { return false; } } /// /// Detect format from a JSON string payload. /// public static Format DetectFromString(string json) { if (string.IsNullOrWhiteSpace(json)) return Format.Unknown; // Try parse as JToken to handle arrays and objects. JToken token; bool ndjsonProbe = false; try { token = JToken.Parse(json); } catch (JsonReaderException) { // Could be NDJSON (newline-delimited JSON). Try to detect by reading first non-empty line. var firstLine = ReadFirstNonEmptyLine(json); if (firstLine != null) { try { token = JToken.Parse(firstLine); // If we successfully parsed the first line of a non-JSON-root document, it's NDJSON -> treat as GeoJsonSeq. ndjsonProbe = true; } catch { return Format.Unknown; } } else { return Format.Unknown; } } // If we discovered this by probing the first line of NDJSON, consider it a GeoJsonSeq. if (ndjsonProbe) { return Format.GeoJsonSeq; } // GeoJsonSeq: top-level array of GeoJSON objects if (token.Type == JTokenType.Array) { return Format.GeoJsonSeq; } if (token.Type != JTokenType.Object) { return Format.Unknown; } var obj = (JObject)token; // TopoJSON: "type": "Topology" var typeProp = obj.Property("type", StringComparison.OrdinalIgnoreCase); if (typeProp != null) { var typeVal = typeProp.Value?.ToString(); if (string.Equals(typeVal, "Topology", StringComparison.OrdinalIgnoreCase)) return Format.TopoJson; if (string.Equals(typeVal, "FeatureCollection", StringComparison.OrdinalIgnoreCase)) return Format.GeoJson; } // EsriJson: presence of "features" and "spatialReference" if (obj.Property("features", StringComparison.OrdinalIgnoreCase) != null && obj.Property("spatialReference", StringComparison.OrdinalIgnoreCase) != null) { return Format.EsriJson; } // Some Esri JSON variants include "features" even without spatialReference if (obj.Property("features", StringComparison.OrdinalIgnoreCase) != null) { return Format.EsriJson; } return Format.Unknown; } /// /// Return the first non-empty line from the provided text (used to sniff NDJSON). /// private static string ReadFirstNonEmptyLine(string text) { using (var sr = new StringReader(text)) { string line; while ((line = sr.ReadLine()) != null) { if (!string.IsNullOrWhiteSpace(line)) return line; } } return null; } } } }