Skip to content

Commit

Permalink
Refactor to explicit serializer
Browse files Browse the repository at this point in the history
- Global one can be affected by global settings that can mess things up
- Don't mess with external code when we add contracts/custom serializers etc
  • Loading branch information
mausch committed Feb 25, 2025
1 parent 151e82b commit 2185653
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
2 changes: 1 addition & 1 deletion metabase-exporter/ApiObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
23 changes: 21 additions & 2 deletions metabase-exporter/GeneralExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -40,8 +42,10 @@ public static async Task<IReadOnlyList<TResult>> Sequence<TResult>(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<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, TKey key, string error)
Expand All @@ -53,4 +57,19 @@ public static TValue GetOrThrow<TKey, TValue>(this IReadOnlyDictionary<TKey, TVa
throw new KeyNotFoundException($"Key not found: {key}\n{error}");
}

public static T DeserializeObject<T>(this JsonSerializer serializer, string rawJson)
{
using var reader = new StringReader(rawJson);
using var jsonReader = new JsonTextReader(reader);
return serializer.Deserialize<T>(jsonReader);
}

public static string SerializeObject<T>(this JsonSerializer serializer, T obj)
{
using var writer = new StringWriter();
using var jsonWriter = new JsonTextWriter(writer);
serializer.Serialize(jsonWriter, obj);
return writer.ToString();
}

}
22 changes: 11 additions & 11 deletions metabase-exporter/MetabaseApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ HttpRequestMessage request() =>
var response = await sessionManager.Send(request);
try
{
return JsonConvert.DeserializeObject<Card>(response);
return Program.Serializer.DeserializeObject<Card>(response);
}
catch (JsonSerializationException e)
{
Expand Down Expand Up @@ -67,7 +67,7 @@ HttpRequestMessage request() =>
var response = await sessionManager.Send(request);
try
{
return JsonConvert.DeserializeObject<Dashboard>(response);
return Program.Serializer.DeserializeObject<Dashboard>(response);
}
catch (JsonSerializationException e)
{
Expand Down Expand Up @@ -141,7 +141,7 @@ HttpRequestMessage request1() =>
var response = await sessionManager.Send(request1);
try
{
return JsonConvert.DeserializeObject<DashboardCard>(response);
return Program.Serializer.DeserializeObject<DashboardCard>(response);
}
catch (JsonSerializationException e)
{
Expand All @@ -159,7 +159,7 @@ HttpRequestMessage request() =>
var response = await sessionManager.Send(request);
try
{
return JsonConvert.DeserializeObject<Collection>(response);
return Program.Serializer.DeserializeObject<Collection>(response);
}
catch (JsonSerializationException e)
{
Expand All @@ -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);
}
Expand All @@ -191,7 +191,7 @@ public async Task<IReadOnlyList<Card>> GetAllCards()
HttpRequestMessage request() => new HttpRequestMessage(HttpMethod.Get, new Uri("/api/card", UriKind.Relative));
var response = await sessionManager.Send(request);
try {
return JsonConvert.DeserializeObject<Card[]>(response);
return Program.Serializer.DeserializeObject<Card[]>(response);
} catch (JsonReaderException e) {
throw new MetabaseApiException($"Error parsing response for {nameof(Card)} from:\n{response}", e);
}
Expand All @@ -204,7 +204,7 @@ public async Task<IReadOnlyList<Collection>> GetAllCollections()
response = response.Replace("\"id\":\"root\"", "\"id\":\"0\"");
try
{
return JsonConvert.DeserializeObject<Collection[]>(response);
return Program.Serializer.DeserializeObject<Collection[]>(response);
}
catch (JsonSerializationException e)
{
Expand All @@ -217,7 +217,7 @@ public async Task<IReadOnlyList<Dashboard>> GetAllDashboards()
HttpRequestMessage request() => new HttpRequestMessage(HttpMethod.Get, new Uri("/api/dashboard", UriKind.Relative));
var response = await sessionManager.Send(request);
try {
var dashboards = JsonConvert.DeserializeObject<Dashboard[]>(response);
var dashboards = Program.Serializer.DeserializeObject<Dashboard[]>(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));
}
Expand All @@ -233,7 +233,7 @@ public async Task<Dashboard> GetDashboard(DashboardId dashboardId)
var response = await sessionManager.Send(request);
try
{
return JsonConvert.DeserializeObject<Dashboard>(response);
return Program.Serializer.DeserializeObject<Dashboard>(response);
}
catch (JsonSerializationException e)
{
Expand All @@ -247,7 +247,7 @@ public async Task<IReadOnlyList<DatabaseId>> GetAllDatabaseIds()
var response = await sessionManager.Send(request);
try
{
var databases = JsonConvert.DeserializeObject<JObject>(response)["data"];
var databases = Program.Serializer.DeserializeObject<JObject>(response)["data"];
return databases.Select(d => new DatabaseId((int) d["id"])).ToImmutableList();
}
catch (JsonSerializationException e)
Expand All @@ -262,7 +262,7 @@ public async Task<RunCardResult> RunCard(CardId card)
var response = await sessionManager.Send(request);
try
{
return JsonConvert.DeserializeObject<RunCardResult>(response);
return Program.Serializer.DeserializeObject<RunCardResult>(response);
}
catch (JsonSerializationException e)
{
Expand Down
19 changes: 17 additions & 2 deletions metabase-exporter/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,40 @@ 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,
import: api.Import,
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}");
}

static async Task Import(this MetabaseApi api, Config.Import import)
{
var rawState = File.ReadAllText(import.InputFilename);
var state = JsonConvert.DeserializeObject<MetabaseState>(rawState);
var state = Program.Serializer.DeserializeObject<MetabaseState>(rawState);
await api.Import(state, import.DatabaseMapping, import.IgnoredDatabases);
Console.WriteLine($"Done importing from {import.InputFilename} into {import.MetabaseApiSettings.MetabaseApiUrl}");
}
Expand Down

0 comments on commit 2185653

Please sign in to comment.