2323#include < gtest/gtest.h>
2424#include < nlohmann/json.hpp>
2525
26+ #include " iceberg/expression/literal.h"
2627#include " iceberg/json_serde_internal.h"
2728#include " iceberg/name_mapping.h"
2829#include " iceberg/partition_spec.h"
2930#include " iceberg/schema.h"
31+ #include " iceberg/schema_field.h"
3032#include " iceberg/snapshot.h"
3133#include " iceberg/sort_field.h"
3234#include " iceberg/sort_order.h"
3537#include " iceberg/table_update.h"
3638#include " iceberg/test/matchers.h"
3739#include " iceberg/transform.h"
40+ #include " iceberg/type.h"
3841#include " iceberg/util/formatter.h" // IWYU pragma: keep
3942#include " iceberg/util/macros.h" // IWYU pragma: keep
4043#include " iceberg/util/timepoint.h"
44+ #include " iceberg/util/uuid.h"
4145
4246namespace iceberg {
4347
@@ -71,6 +75,11 @@ Result<std::unique_ptr<NameMapping>> FromJsonHelper(const nlohmann::json& json)
7175 return NameMappingFromJson (json);
7276}
7377
78+ template <>
79+ Result<std::unique_ptr<SchemaField>> FromJsonHelper (const nlohmann::json& json) {
80+ return FieldFromJson (json);
81+ }
82+
7483// Helper function to reduce duplication in testing
7584template <typename T>
7685void TestJsonConversion (const T& obj, const nlohmann::json& expected_json) {
@@ -85,6 +94,66 @@ void TestJsonConversion(const T& obj, const nlohmann::json& expected_json) {
8594
8695} // namespace
8796
97+ // Pins the wire format produced by ToJson(SchemaField) / FieldFromJson for
98+ // `initial-default` and `write-default`, including the absence of the keys when a
99+ // field carries no defaults.
100+ TEST (JsonInternalTest, SchemaFieldDefaultValues) {
101+ // Both defaults present.
102+ SchemaField with_both (/* field_id=*/ 1 , " id" , int32 (), /* optional=*/ false , /* doc=*/ {},
103+ std::make_shared<const Literal>(Literal::Int (42 )),
104+ std::make_shared<const Literal>(Literal::Int (7 )));
105+ TestJsonConversion (
106+ with_both,
107+ R"( {"id":1,"name":"id","required":true,"type":"int","initial-default":42,"write-default":7})" _json);
108+
109+ // Only an initial-default; write-default must not appear in the JSON.
110+ SchemaField initial_only (/* field_id=*/ 2 , " name" , string (), /* optional=*/ true ,
111+ /* doc=*/ {},
112+ std::make_shared<const Literal>(Literal::String (" n/a" )),
113+ /* write_default=*/ nullptr );
114+ TestJsonConversion (
115+ initial_only,
116+ R"( {"id":2,"name":"name","required":false,"type":"string","initial-default":"n/a"})" _json);
117+
118+ // No defaults; neither key may appear.
119+ SchemaField no_defaults (/* field_id=*/ 3 , " plain" , int32 (), /* optional=*/ false );
120+ TestJsonConversion (no_defaults,
121+ R"( {"id":3,"name":"plain","required":true,"type":"int"})" _json);
122+ }
123+
124+ // Round-trips a field carrying both defaults through ToJson -> FieldFromJson for
125+ // every primitive type, exercising the per-type single-value serialization the
126+ // default path reuses (date/timestamp/decimal/uuid/binary have non-trivial wire
127+ // encodings).
128+ TEST (JsonInternalTest, SchemaFieldDefaultValuesRoundTripAllTypes) {
129+ ICEBERG_UNWRAP_OR_FAIL (auto uuid_value,
130+ Uuid::FromString (" f79c3e09-677c-4bbd-a479-3f349cb785e7" ));
131+ std::vector<std::pair<std::shared_ptr<Type>, Literal>> cases;
132+ cases.emplace_back (boolean (), Literal::Boolean (true ));
133+ cases.emplace_back (int32 (), Literal::Int (-7 ));
134+ cases.emplace_back (int64 (), Literal::Long (1234567890123LL ));
135+ cases.emplace_back (float32 (), Literal::Float (1 .5f ));
136+ cases.emplace_back (float64 (), Literal::Double (2.5 ));
137+ cases.emplace_back (date (), Literal::Date (19738 ));
138+ cases.emplace_back (timestamp (), Literal::Timestamp (1719446400000000LL ));
139+ cases.emplace_back (string (), Literal::String (" hello" ));
140+ cases.emplace_back (decimal (9 , 2 ), Literal::Decimal (12345 , 9 , 2 ));
141+ cases.emplace_back (fixed (3 ), Literal::Fixed ({0x01 , 0x02 , 0x03 }));
142+ cases.emplace_back (binary (), Literal::Binary ({0xDE , 0xAD , 0xBE , 0xEF }));
143+ cases.emplace_back (uuid (), Literal::UUID (uuid_value));
144+
145+ int32_t field_id = 1 ;
146+ for (const auto & [type, literal] : cases) {
147+ SchemaField field (field_id++, " f" , type, /* optional=*/ false , /* doc=*/ {},
148+ std::make_shared<const Literal>(literal),
149+ std::make_shared<const Literal>(literal));
150+ auto json = ToJson (field);
151+ ICEBERG_UNWRAP_OR_FAIL (auto parsed, FieldFromJson (json));
152+ EXPECT_EQ (field, *parsed)
153+ << " round-trip mismatch for type " << type->ToString () << " , json=" << json.dump ();
154+ }
155+ }
156+
88157TEST (JsonInternalTest, SortField) {
89158 auto identity_transform = Transform::Identity ();
90159
0 commit comments