Skip to content

Serialize nonempty dicts as lists-of-pairs for untyped table insertion#95

Closed
sl-at-ibm wants to merge 1 commit intomainfrom
SL-untyped-table-dicts-as-lists
Closed

Serialize nonempty dicts as lists-of-pairs for untyped table insertion#95
sl-at-ibm wants to merge 1 commit intomainfrom
SL-untyped-table-dicts-as-lists

Conversation

@sl-at-ibm
Copy link
Collaborator

@sl-at-ibm sl-at-ibm commented Mar 17, 2026

Fixes #96 .

As remarked by @skedwards88 , untyped table insertion seems not to serialize correctly (which raises API exceptions for non-string keys).
This PR aims at fixing this. To do so, a new converter is created, specific to the case, and injected when needed in the serialization stack. Note this is serialization only (no deserialization).

I think this works as intended. The attached tests (which should be followed by manual payload inspection in the logs) ensure that:

Tests (ahem, paste them somewhere to run them):

public class SBook
{
  [ColumnPrimaryKey(1)]
  [ColumnName("title")]
  public string? Title { get; set; }

  [ColumnPrimaryKey(2)]
  [ColumnName("author")]
  public string? Author { get; set; }

  [ColumnName("map_column_int_str")]
  public Dictionary<int, string>? MapColumnIntStr { get; set; }

  [ColumnName("map_column_str_str")]
  public Dictionary<string, string>? MapColumnStrStr { get; set; }
}



[...]


    [Fact(Skip = "Unskip manually when needed.")]
    public async Task SteoNonstringMapTableInsertionTests()
    {

        var tableName = "steo_nonstrmap_insertiontest";

        try
        {
            var table = await fixture.Database.CreateTableAsync<SBook>(tableName,
                new CreateTableCommandOptions() { IfNotExists = true });
            var untypedTable = fixture.Database.GetTable(tableName);

            // typed insertion
            var row = new SBook()
            {
                MapColumnIntStr = new Dictionary<int, string>
                {
                    { 1, "value1" },
                    { 2, "value2" },
                },
                MapColumnStrStr = new Dictionary<string, string>
                {
                    { "key1", "value1" },
                    { "key2", "value2" },
                },
                Title = "Once in a Living Memory",
                Author = "Kayla McMaster",
            };
            await table.InsertOneAsync(row);
            var rowE = new SBook()
            {
                MapColumnIntStr = new Dictionary<int, string>{},
                MapColumnStrStr = new Dictionary<string, string>{},
                Title = "emptyT",
                Author = "emptyA",
            };
            await table.InsertOneAsync(rowE);

            // untyped insertion
            var untypedRow = new Row()
            {
                {
                    "map_column_int_str",
                    new Dictionary<int, string> { { 1, "value1" }, { 2, "value2" } }
                },
                {
                    "map_column_str_str",
                    new Dictionary<string, string>
                    {
                        { "key1", "value1" },
                        { "key2", "value2" },
                    }
                },
                { "title", "UNTY in a Living Memory" },
                { "author", "UNTYP McMaster" },
            };
            await untypedTable.InsertOneAsync(untypedRow);

            var untypedRowE = new Row()
            {
                {
                    "map_column_int_str",
                    new Dictionary<int, string> {}
                },
                {
                    "map_column_str_str",
                    new Dictionary<string, string> {}
                },
                { "title", "UNTYemptyT" },
                { "author", "UNTYemptyA" },
            };

            await untypedTable.InsertOneAsync(untypedRowE);
        }
        finally
        {
            await fixture.Database.DropTableAsync(tableName);
        }
    }

    [Fact(Skip = "Unskip manually when needed.")]
    public async Task SteoCollectionDictSerializationSanityTest()
    {
        var collName = "test_nestedobj_coll";
        try
        {
            var collection = await fixture.Database.CreateCollectionAsync<SteoNestedCollectionObject>(collName);
            var doc = new SteoNestedCollectionObject
            {
                _id = "one_typed",
                subobject = new SteoNestedCollectionSubobject { sfield = "sf0", ifield = 999 },
                subobject_map = new Dictionary<string, SteoNestedCollectionSubobject> {
                    ["key1"] = new SteoNestedCollectionSubobject { sfield = "sf11", ifield = 11 },
                    ["key2"] = new SteoNestedCollectionSubobject { sfield = "sf12", ifield = 12 }
                }
            };
            await collection.InsertOneAsync(doc);

            // untyped form
            var untypedCollection = fixture.Database.GetCollection(collName);
            var untypedDoc = new Document()
            {
                {"_id", "two_untyped"},
                {
                    "subobject",
                    new Dictionary<string, object>
                    {
                        {"sfield", "sf0"},
                        {"ifield", 999}
                    }
                },
                {
                    "subobject_map",
                    new Dictionary<string, object>
                    {
                        {
                            "key1",
                            new Dictionary<string, object>
                            {
                                {"sfield", "sf11"},
                                {"ifield", 11}
                            }
                        },
                        {
                            "key2",
                            new Dictionary<string, object>
                            {
                                {"sfield", "sf12"},
                                {"ifield", 12}
                            }
                        }
                    }
                }
            };
            await untypedCollection.InsertOneAsync(untypedDoc);
        }
        finally
        {
            await fixture.Database.DropCollectionAsync(collName);
        }
    }

@sl-at-ibm sl-at-ibm requested a review from toptobes March 17, 2026 18:08
@toptobes
Copy link
Collaborator

Will become a part of #92

@sl-at-ibm
Copy link
Collaborator Author

@toptobes sorry for not including these right away!

public class SteoNestedCollectionSubobject
{
    public string sfield { get; set; }
    public int ifield { get; set; }
}

public class SteoNestedCollectionObject
{
    [DocumentId]
    public string _id { get; set; }
    public SteoNestedCollectionSubobject subobject { get; set; }
    public Dictionary<string, SteoNestedCollectionSubobject> subobject_map { get; set; }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serialization of non-string-keyed dictionaries for untyped tables fails

2 participants