diff --git a/doc/manual/builtins.xml b/doc/manual/builtins.xml index 6a472291..b289c6f0 100644 --- a/doc/manual/builtins.xml +++ b/doc/manual/builtins.xml @@ -257,6 +257,22 @@ stdenv.mkDerivation { + builtins.fromJSON e + + Convert a JSON string to a Nix + value. For example, + + +builtins.fromJSON ''{"x": [1, 2, 3], "y": null}'' + + + returns the value { x = [ 1 2 3 ]; y = null; + }. Floating point numbers are not + supported. + + + + builtins.getAttr s set @@ -762,6 +778,7 @@ in foo + builtins.toPath s Convert the string value diff --git a/src/libexpr/json-to-value.cc b/src/libexpr/json-to-value.cc new file mode 100644 index 00000000..7f60e7cc --- /dev/null +++ b/src/libexpr/json-to-value.cc @@ -0,0 +1,144 @@ +#include "config.h" +#include "json-to-value.hh" + +#include + +namespace nix { + + +static void skipWhitespace(const char * & s) +{ + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; +} + + +#if HAVE_BOEHMGC +typedef std::vector > ValueVector; +#else +typedef std::vector ValueVector; +#endif + + +static string parseJSONString(const char * & s) +{ + string res; + if (*s++ != '"') throw JSONParseError("expected JSON string"); + while (*s != '"') { + if (!*s) throw JSONParseError("got end-of-string in JSON string"); + if (*s == '\\') { + s++; + if (*s == '"') res += '"'; + else if (*s == '\\') res += '\\'; + else if (*s == '/') res += '/'; + else if (*s == '/') res += '/'; + else if (*s == 'b') res += '\b'; + else if (*s == 'f') res += '\f'; + else if (*s == 'n') res += '\n'; + else if (*s == 'r') res += '\r'; + else if (*s == 't') res += '\t'; + else if (*s == 'u') throw JSONParseError("\\u characters in JSON strings are currently not supported"); + else throw JSONParseError("invalid escaped character in JSON string"); + s++; + } else + res += *s++; + } + s++; + return res; +} + + +static void parseJSON(EvalState & state, const char * & s, Value & v) +{ + skipWhitespace(s); + + if (!*s) throw JSONParseError("expected JSON value"); + + if (*s == '[') { + s++; + ValueVector values; + values.reserve(128); + skipWhitespace(s); + while (1) { + if (values.empty() && *s == ']') break; + Value * v2 = state.allocValue(); + parseJSON(state, s, *v2); + values.push_back(v2); + skipWhitespace(s); + if (*s == ']') break; + if (*s != ',') throw JSONParseError("expected `,' or `]' after JSON array element"); + s++; + } + s++; + state.mkList(v, values.size()); + for (size_t n = 0; n < values.size(); ++n) + v.list.elems[n] = values[n]; + } + + else if (*s == '{') { + s++; + state.mkAttrs(v, 1); + while (1) { + skipWhitespace(s); + if (v.attrs->empty() && *s == '}') break; + string name = parseJSONString(s); + skipWhitespace(s); + if (*s != ':') throw JSONParseError("expected `:' in JSON object"); + s++; + Value * v2 = state.allocValue(); + parseJSON(state, s, *v2); + v.attrs->push_back(Attr(state.symbols.create(name), v2)); + skipWhitespace(s); + if (*s == '}') break; + if (*s != ',') throw JSONParseError("expected `,' or `}' after JSON member"); + s++; + } + v.attrs->sort(); + s++; + } + + else if (*s == '"') { + mkString(v, parseJSONString(s)); + } + + else if (isdigit(*s) || *s == '-') { + bool neg = false; + if (*s == '-') { + neg = true; + if (!*++s) throw JSONParseError("unexpected end of JSON number"); + } + NixInt n = 0; + // FIXME: detect overflow + while (isdigit(*s)) n = n * 10 + (*s++ - '0'); + if (*s == '.' || *s == 'e') throw JSONParseError("floating point JSON numbers are not supported"); + mkInt(v, neg ? -n : n); + } + + else if (strncmp(s, "true", 4) == 0) { + s += 4; + mkBool(v, true); + } + + else if (strncmp(s, "false", 5) == 0) { + s += 5; + mkBool(v, false); + } + + else if (strncmp(s, "null", 4) == 0) { + s += 4; + mkNull(v); + } + + else throw JSONParseError("unrecognised JSON value"); +} + + +void parseJSON(EvalState & state, const string & s_, Value & v) +{ + const char * s = s_.c_str(); + parseJSON(state, s, v); + skipWhitespace(s); + if (*s) throw JSONParseError(format("expected end-of-string while parsing JSON value: %1%") % s); +} + + +} diff --git a/src/libexpr/json-to-value.hh b/src/libexpr/json-to-value.hh new file mode 100644 index 00000000..33f35b16 --- /dev/null +++ b/src/libexpr/json-to-value.hh @@ -0,0 +1,13 @@ +#pragma once + +#include "eval.hh" + +#include + +namespace nix { + +MakeError(JSONParseError, EvalError) + +void parseJSON(EvalState & state, const string & s, Value & v); + +} diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index ff82f36b..01c7ca44 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -6,6 +6,7 @@ #include "archive.hh" #include "value-to-xml.hh" #include "value-to-json.hh" +#include "json-to-value.hh" #include "names.hh" #include "eval-inline.hh" @@ -775,6 +776,14 @@ static void prim_toJSON(EvalState & state, const Pos & pos, Value * * args, Valu } +/* Parse a JSON string to a value. */ +static void prim_fromJSON(EvalState & state, const Pos & pos, Value * * args, Value & v) +{ + string s = state.forceStringNoCtx(*args[0], pos); + parseJSON(state, s, v); +} + + /* Store a string in the Nix store as a source file that can be used as an input by derivations. */ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v) @@ -1396,6 +1405,7 @@ void EvalState::createBaseEnv() // Creating files addPrimOp("__toXML", 1, prim_toXML); addPrimOp("__toJSON", 1, prim_toJSON); + addPrimOp("__fromJSON", 1, prim_fromJSON); addPrimOp("__toFile", 2, prim_toFile); addPrimOp("__filterSource", 2, prim_filterSource); diff --git a/tests/lang/eval-okay-fromjson.exp b/tests/lang/eval-okay-fromjson.exp new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/tests/lang/eval-okay-fromjson.exp @@ -0,0 +1 @@ +true diff --git a/tests/lang/eval-okay-fromjson.nix b/tests/lang/eval-okay-fromjson.nix new file mode 100644 index 00000000..5ed0c1c4 --- /dev/null +++ b/tests/lang/eval-okay-fromjson.nix @@ -0,0 +1,32 @@ +# RFC 7159, section 13. +builtins.fromJSON + '' + { + "Image": { + "Width": 800, + "Height": 600, + "Title": "View from 15th Floor", + "Thumbnail": { + "Url": "http://www.example.com/image/481989943", + "Height": 125, + "Width": 100 + }, + "Animated" : false, + "IDs": [116, 943, 234, 38793, true ,false,null, -100] + } + } + '' +== + { Image = + { Width = 800; + Height = 600; + Title = "View from 15th Floor"; + Thumbnail = + { Url = http://www.example.com/image/481989943; + Height = 125; + Width = 100; + }; + Animated = false; + IDs = [ 116 943 234 38793 true false null (0-100) ]; + }; + }