diff --git a/metabase-exporter/ApiObjects.cs b/metabase-exporter/ApiObjects.cs index c8deb11..1816b19 100644 --- a/metabase-exporter/ApiObjects.cs +++ b/metabase-exporter/ApiObjects.cs @@ -23,7 +23,7 @@ public class Card public ResultMetadata[] ResultMetadata { get; set; } [JsonProperty("metadata_checksum")] - public string MetadataChecksum => GeneralExtensions.MD5Base64(JsonConvert.SerializeObject(ResultMetadata)); + public string MetadataChecksum => GeneralExtensions.MD5Base64(Program.Serializer.SerializeObject(ResultMetadata)); [JsonProperty("database_id")] public DatabaseId DatabaseId { get; set; } diff --git a/metabase-exporter/GeneralExtensions.cs b/metabase-exporter/GeneralExtensions.cs index ac21709..989ea5c 100644 --- a/metabase-exporter/GeneralExtensions.cs +++ b/metabase-exporter/GeneralExtensions.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; namespace metabase_exporter; @@ -40,8 +42,10 @@ public static async Task> Sequence(this IEnumera public static string MD5Base64(string x) { - var md5Value = System.Security.Cryptography.MD5.HashData(Encoding.UTF8.GetBytes(x)); - return Convert.ToBase64String(md5Value); + using (var md5 = System.Security.Cryptography.MD5.Create()) { + byte[] md5Value = md5.ComputeHash(Encoding.UTF8.GetBytes(x)); + return Convert.ToBase64String(md5Value); + } } public static TValue GetOrThrow(this IReadOnlyDictionary dict, TKey key, string error) @@ -53,4 +57,19 @@ public static TValue GetOrThrow(this IReadOnlyDictionary(this JsonSerializer serializer, string rawJson) + { + using var reader = new StringReader(rawJson); + using var jsonReader = new JsonTextReader(reader); + return serializer.Deserialize(jsonReader); + } + + public static string SerializeObject(this JsonSerializer serializer, T obj) + { + using var writer = new StringWriter(); + using var jsonWriter = new JsonTextWriter(writer); + serializer.Serialize(jsonWriter, obj); + return writer.ToString(); + } + } \ No newline at end of file diff --git a/metabase-exporter/MetabaseApi.cs b/metabase-exporter/MetabaseApi.cs index e527c02..a5e39cb 100644 --- a/metabase-exporter/MetabaseApi.cs +++ b/metabase-exporter/MetabaseApi.cs @@ -32,7 +32,7 @@ HttpRequestMessage request() => var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -67,7 +67,7 @@ HttpRequestMessage request() => var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -141,7 +141,7 @@ HttpRequestMessage request1() => var response = await sessionManager.Send(request1); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -159,7 +159,7 @@ HttpRequestMessage request() => var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -169,7 +169,7 @@ HttpRequestMessage request() => static (StringContent HttpContent, string Json) ToJsonContent(object o) { - var json = JsonConvert.SerializeObject(o); + var json = Program.Serializer.SerializeObject(o); var content = new StringContent(json, Encoding.UTF8, "application/json"); return (content, json); } @@ -191,7 +191,7 @@ public async Task> GetAllCards() HttpRequestMessage request() => new HttpRequestMessage(HttpMethod.Get, new Uri("/api/card", UriKind.Relative)); var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonReaderException e) { throw new MetabaseApiException($"Error parsing response for {nameof(Card)} from:\n{response}", e); } @@ -204,7 +204,7 @@ public async Task> GetAllCollections() response = response.Replace("\"id\":\"root\"", "\"id\":\"0\""); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -217,7 +217,7 @@ public async Task> GetAllDashboards() HttpRequestMessage request() => new HttpRequestMessage(HttpMethod.Get, new Uri("/api/dashboard", UriKind.Relative)); var response = await sessionManager.Send(request); try { - var dashboards = JsonConvert.DeserializeObject(response); + var dashboards = Program.Serializer.DeserializeObject(response); // the endpoint that returns all dashboards does not include all detail for each dashboard return await dashboards.Traverse(async dashboard => await GetDashboard(dashboard.Id)); } @@ -233,7 +233,7 @@ public async Task GetDashboard(DashboardId dashboardId) var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { @@ -247,7 +247,7 @@ public async Task> GetAllDatabaseIds() var response = await sessionManager.Send(request); try { - var databases = JsonConvert.DeserializeObject(response)["data"]; + var databases = Program.Serializer.DeserializeObject(response)["data"]; return databases.Select(d => new DatabaseId((int) d["id"])).ToImmutableList(); } catch (JsonSerializationException e) @@ -262,7 +262,7 @@ public async Task RunCard(CardId card) var response = await sessionManager.Send(request); try { - return JsonConvert.DeserializeObject(response); + return Program.Serializer.DeserializeObject(response); } catch (JsonSerializationException e) { diff --git a/metabase-exporter/Program.cs b/metabase-exporter/Program.cs index 3e025fd..f503daa 100644 --- a/metabase-exporter/Program.cs +++ b/metabase-exporter/Program.cs @@ -24,6 +24,16 @@ public static async Task Main(string[] args) var rawConfig = builder.Build(); var config = ParseConfig(rawConfig); + + _serializer = JsonSerializer.Create(new JsonSerializerSettings + { + //Formatting = Formatting.Indented // don't set this, it will mess checksums + }); + _indentedSerializer = JsonSerializer.Create(new JsonSerializerSettings + { + Formatting = Formatting.Indented, + }); + var api = await InitApi(config.MetabaseApiSettings); await config.Switch( export: api.Export, @@ -31,10 +41,15 @@ await config.Switch( testQuestions: _ => api.TestQuestions()); } + static JsonSerializer _serializer; + public static JsonSerializer Serializer => _serializer; + + static JsonSerializer _indentedSerializer; + static async Task Export(this MetabaseApi api, Config.Export export) { var state = await api.Export(export.ExcludePersonalCollections); - var stateJson = JsonConvert.SerializeObject(state, Formatting.Indented); + var stateJson = _indentedSerializer.SerializeObject(state); File.WriteAllText(path: export.OutputFilename, contents: stateJson); Console.WriteLine($"Exported current state for {export.MetabaseApiSettings.MetabaseApiUrl} to {export.OutputFilename}"); } @@ -42,7 +57,7 @@ static async Task Export(this MetabaseApi api, Config.Export export) static async Task Import(this MetabaseApi api, Config.Import import) { var rawState = File.ReadAllText(import.InputFilename); - var state = JsonConvert.DeserializeObject(rawState); + var state = Program.Serializer.DeserializeObject(rawState); await api.Import(state, import.DatabaseMapping, import.IgnoredDatabases); Console.WriteLine($"Done importing from {import.InputFilename} into {import.MetabaseApiSettings.MetabaseApiUrl}"); }