void putValue()

in extensions/standard-processors/utils/JoltUtils.cpp [819:948]


void putValue(const Spec::Context& ctx, const Spec::Destination& dest, const rapidjson::Value& val, rapidjson::Document& output) {
  std::vector<std::pair<std::string, Spec::MemberType>> evaled_dest;

  for (auto& [templ, type] : dest) {
    if (auto* val_ref = std::get_if<Spec::ValueRef>(&templ)) {
      auto* root = ctx.find(val_ref->first);
      if (!root) {
        throw Exception(GENERAL_EXCEPTION, fmt::format("Could not find ancestor at index {} from {}", val_ref->first, ctx.path()));
      }
      if (!root->node) {
        return;
      }
      std::reference_wrapper<const rapidjson::Value> member_value = std::ref(*root->node);
      if (auto inner_member_value = resolvePath(ctx, ctx.path(), *root->node, val_ref->second)) {
        member_value = inner_member_value.value();
      } else {
        auto sub_path = toString(ctx, val_ref->second);
        ctx.log([&] (auto logger) {
          logger->log_trace("Could not find member at @({},{} as {}) from {}", val_ref->first, sub_path.first, sub_path.second, ctx.path());
        }, [] (auto) {});
        // do not write anything and do not throw
        return;
      }
      if (type == Spec::MemberType::INDEX) {
        size_t idx{};
        if (member_value.get().IsUint64()) {
          idx = gsl::narrow<size_t>(member_value.get().GetUint64());
        } else if (member_value.get().IsInt64()) {
          int64_t idx_val = member_value.get().GetInt64();
          if (idx_val < 0) {
            return;
          }
          idx = gsl::narrow<size_t>(idx_val);
        } else if (member_value.get().IsDouble()) {
          // no words
          double idx_val = member_value.get().GetDouble();
          if (idx_val < 0) {
            return;
          }
          idx = static_cast<size_t>(idx_val);
        } else if (member_value.get().IsString()) {
          // amazing... not
          if (isAllDigits(member_value.get().GetString(), member_value.get().GetString() + member_value.get().GetStringLength())) {
            idx = std::stoull(std::string{member_value.get().GetString(), member_value.get().GetStringLength()});
          } else {
            return;
          }
        } else {
          return;
        }
        evaled_dest.push_back({std::to_string(idx), type});
      } else {
        auto member = jsonValueToString(member_value);
        if (!member) {
          return;
        }
        evaled_dest.push_back({std::move(member.value()), type});
      }
      continue;
    }

    if (auto* match_idx = std::get_if<Spec::MatchingIndex>(&templ)) {
      gsl_Expects(type == Spec::MemberType::INDEX);
      auto* target = ctx.find(*match_idx);
      if (!target) {
        throw Exception(GENERAL_EXCEPTION, fmt::format("Could not find ancestor at index {} from {}", *match_idx, ctx.path()));
      }
      evaled_dest.push_back({std::to_string(target->match_count), type});
      continue;
    }

    // empty segment is self-reference, e.g. a..b == a.b
    if (type == Spec::MemberType::FIELD && get<Spec::Template>(templ).empty()) {
      continue;
    }

    evaled_dest.push_back({get<Spec::Template>(templ).eval(ctx), type});
  }

  std::reference_wrapper<rapidjson::Value> target = output;
  for (auto& [member, type] : evaled_dest) {
    if (type == Spec::MemberType::INDEX) {
      if (!target.get().IsArray()) {
        if (!target.get().IsNull()) {
          throw Exception(GENERAL_EXCEPTION, "Cannot write based on index into non-array");
        }
        target.get().SetArray();
      }
      size_t idx = [&, member_ptr = &member] () -> size_t {
        if (member_ptr->empty()) {
          // an empty index like "field.inner[]" means to append to the end
          return gsl::narrow<size_t>(target.get().Size());
        }
        try {
          return std::stoull(*member_ptr);
        } catch (const std::exception&) {
          throw Exception(GENERAL_EXCEPTION, fmt::format("Could not convert '{}' to index", *member_ptr));
        }
      }();
      target.get().Reserve(gsl::narrow<rapidjson::SizeType>(idx + 1), output.GetAllocator());
      for (size_t arr_idx = target.get().Size(); arr_idx <= idx; ++arr_idx) {
        target.get().PushBack(rapidjson::Value{}, output.GetAllocator());
      }
      target = target.get()[gsl::narrow<rapidjson::SizeType>(idx)];
    } else {
      if (!target.get().IsObject()) {
        if (!target.get().IsNull()) {
          throw Exception(GENERAL_EXCEPTION, "Cannot write member into non-object");
        }
        target.get().SetObject();
      }
      if (!target.get().HasMember(member)) {
        target.get().AddMember(rapidjson::Value{member.c_str(), gsl::narrow<rapidjson::SizeType>(member.size()), output.GetAllocator()}, rapidjson::Value{}, output.GetAllocator());
      }
      target = target.get()[member];
    }
  }

  if (!target.get().IsNull()) {
    if (!target.get().IsArray()) {
      // put it in an array
      rapidjson::Value tmp{target.get().Move(), output.GetAllocator()};
      target.get().SetArray();
      target.get().GetArray().PushBack(tmp.Move(), output.GetAllocator());
    }
    target.get().PushBack(rapidjson::Value{}, output.GetAllocator());
    target = target.get()[target.get().GetArray().Size() - 1];
  }
  target.get().CopyFrom(val, output.GetAllocator());
}