From d95ff93e70528dca9c06111c65dd7ebe8e3152e7 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Thu, 24 Jul 2025 20:17:38 -0700 Subject: [PATCH 1/4] Add test case for json with comments --- .../lang/eval-okay-fromjson-comments.exp | 1 + .../lang/eval-okay-fromjson-comments.nix | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/functional/lang/eval-okay-fromjson-comments.exp create mode 100644 tests/functional/lang/eval-okay-fromjson-comments.nix diff --git a/tests/functional/lang/eval-okay-fromjson-comments.exp b/tests/functional/lang/eval-okay-fromjson-comments.exp new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/functional/lang/eval-okay-fromjson-comments.exp @@ -0,0 +1 @@ +true diff --git a/tests/functional/lang/eval-okay-fromjson-comments.nix b/tests/functional/lang/eval-okay-fromjson-comments.nix new file mode 100644 index 000000000..3ad4e59dc --- /dev/null +++ b/tests/functional/lang/eval-okay-fromjson-comments.nix @@ -0,0 +1,61 @@ +builtins.fromJSON '' + { + // This is a comment + "Video": { + /* Multi line comment single line */ + "Title": "The Penguin Chronicles", + "Width": 1920, + "Height": 1080, + /** + * Multi line comment + * with multiple lines + */ + "EmbeddedData": [3.14159, 23493,null, true ,false, -10], + "Thumb": { + "Url": "http://www.example.com/video/5678931", + "Width": 200, + "Height": 250 + }, + "Animated" : false, + "IDs": [116, 943, 234, 38793, true ,false,null, -100], + "Escapes": "\"\\\/\t\n\r\t", + "Subtitle" : false, + "Latitude": 37.7668, + "Longitude": -122.3959 + } + } +'' == { + Video = { + Title = "The Penguin Chronicles"; + Width = 1920; + Height = 1080; + EmbeddedData = [ + 3.14159 + 23493 + null + true + false + (0 - 10) + ]; + Thumb = { + Url = "http://www.example.com/video/5678931"; + Width = 200; + Height = 250; + }; + Animated = false; + IDs = [ + 116 + 943 + 234 + 38793 + true + false + null + (0 - 100) + ]; + Escapes = "\"\\\/\t\n\r\t"; # supported in JSON but not Nix: \b\f + Subtitle = false; + Latitude = 37.7668; + Longitude = -122.3959; + }; +} From 00921f6e3cd4b8dcdd9be7cea81c52c86d9c0706 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Thu, 24 Jul 2025 20:43:02 -0700 Subject: [PATCH 2/4] Add support for stripping_comments from json. Update documentation of primop --- src/libexpr/json-to-value.cc | 3 ++- src/libexpr/primops.cc | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc index 9c645e7fd..f2fb03569 100644 --- a/src/libexpr/json-to-value.cc +++ b/src/libexpr/json-to-value.cc @@ -204,7 +204,8 @@ public: void parseJSON(EvalState & state, const std::string_view & s_, Value & v) { JSONSax parser(state, v); - bool res = json::sax_parse(s_, &parser); + bool res = json::sax_parse( + s_, &parser, nlohmann::detail::input_format_t::json, /* strict= */ true, /* ignore_comments= */ true); if (!res) throw JSONParseError("Invalid JSON Value"); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index a645f546d..bafaccc83 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2492,6 +2492,8 @@ static RegisterPrimOp primop_fromJSON({ ``` returns the value `{ x = [ 1 2 3 ]; y = null; }`. + + This function supports JSON with comments. )", .fun = prim_fromJSON, }); From 4b78ee44eef88b7d4956f6a754ac1784254bc09c Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Fri, 8 Aug 2025 17:46:38 -0700 Subject: [PATCH 3/4] Move support for comments to fromJSONC --- src/libexpr/include/nix/expr/json-to-value.hh | 9 ++++- src/libexpr/json-to-value.cc | 4 +-- src/libexpr/primops.cc | 35 +++++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/libexpr/include/nix/expr/json-to-value.hh b/src/libexpr/include/nix/expr/json-to-value.hh index 2a2913d68..bccb456b4 100644 --- a/src/libexpr/include/nix/expr/json-to-value.hh +++ b/src/libexpr/include/nix/expr/json-to-value.hh @@ -12,6 +12,13 @@ struct Value; MakeError(JSONParseError, Error); -void parseJSON(EvalState & state, const std::string_view & s, Value & v); +/** + * Parse the string into the Value + * @param state + * @param s + * @param v + * @param allowComments Whether to allow comments in the JSON + */ +void parseJSON(EvalState & state, const std::string_view & s, Value & v, bool allowComments = false); } // namespace nix diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc index f2fb03569..dc569cfb4 100644 --- a/src/libexpr/json-to-value.cc +++ b/src/libexpr/json-to-value.cc @@ -201,11 +201,11 @@ public: } }; -void parseJSON(EvalState & state, const std::string_view & s_, Value & v) +void parseJSON(EvalState & state, const std::string_view & s_, Value & v, bool allowComments) { JSONSax parser(state, v); bool res = json::sax_parse( - s_, &parser, nlohmann::detail::input_format_t::json, /* strict= */ true, /* ignore_comments= */ true); + s_, &parser, nlohmann::detail::input_format_t::json, /* strict= */ true, /* ignore_comments= */ allowComments); if (!res) throw JSONParseError("Invalid JSON Value"); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index bafaccc83..feb5748f0 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2470,11 +2470,11 @@ static RegisterPrimOp primop_toJSON({ }); /* Parse a JSON string to a value. */ -static void prim_fromJSON(EvalState & state, const PosIdx pos, Value ** args, Value & v) +static void prim_fromJSON(EvalState & state, const PosIdx pos, Value ** args, Value & v, bool allow_comments) { auto s = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.fromJSON"); try { - parseJSON(state, s, v); + parseJSON(state, s, v, allow_comments); } catch (JSONParseError & e) { e.addTrace(state.positions[pos], "while decoding a JSON string"); throw; @@ -2492,10 +2492,39 @@ static RegisterPrimOp primop_fromJSON({ ``` returns the value `{ x = [ 1 2 3 ]; y = null; }`. + )", + .fun = + [](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + return prim_fromJSON(state, pos, args, v, false); + }, +}); + +static RegisterPrimOp primop_fromJSONC({ + .name = "__fromJSONC", + .args = {"e"}, + .doc = R"( + Convert a JSON string, potentially with comments, to a Nix value. + For example, + + ```nix + builtins.fromJSONC '' + { + // This is a comment + "x": [1, 2, 3], + /* This is another comment */ + "y": null + } + '' + ``` + + returns the value `{ x = [ 1 2 3 ]; y = null; }`. This function supports JSON with comments. )", - .fun = prim_fromJSON, + .fun = + [](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + return prim_fromJSON(state, pos, args, v, true); + }, }); /* Store a string in the Nix store as a source file that can be used From ccdd97e60a32ec22bb2fbad0de40afdd7a1057f5 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Fri, 8 Aug 2025 17:51:13 -0700 Subject: [PATCH 4/4] Fix test for new builtin --- tests/functional/lang/eval-okay-fromjson-comments.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/lang/eval-okay-fromjson-comments.nix b/tests/functional/lang/eval-okay-fromjson-comments.nix index 3ad4e59dc..34af4d2a5 100644 --- a/tests/functional/lang/eval-okay-fromjson-comments.nix +++ b/tests/functional/lang/eval-okay-fromjson-comments.nix @@ -1,4 +1,4 @@ -builtins.fromJSON '' +builtins.fromJSONC '' { // This is a comment "Video": {