// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
// Licensed under the MIT License:
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "json.h"
#include <capnp/orphan.h>
#include <kj/debug.h>
#include <kj/function.h>
#include <kj/vector.h>
#include <kj/one-of.h>
#include <kj/encoding.h>
#include <kj/map.h>

namespace capnp {

struct JsonCodec::Impl {
  bool prettyPrint = false;
  HasMode hasMode = HasMode::NON_NULL;
  size_t maxNestingDepth = 64;
  bool rejectUnknownFields = false;

  kj::HashMap<Type, HandlerBase*> typeHandlers;
  kj::HashMap<StructSchema::Field, HandlerBase*> fieldHandlers;
  kj::HashMap<Type, kj::Maybe<kj::Own<AnnotatedHandler>>> annotatedHandlers;
  kj::HashMap<Type, kj::Own<AnnotatedEnumHandler>> annotatedEnumHandlers;

  kj::StringTree encodeRaw(JsonValue::Reader value, uint indent, bool& multiline,
                           bool hasPrefix) const {
    switch (value.which()) {
      case JsonValue::NULL_:
        return kj::strTree("null");
      case JsonValue::BOOLEAN:
        return kj::strTree(value.getBoolean());
      case JsonValue::NUMBER:
        return kj::strTree(value.getNumber());

      case JsonValue::STRING:
        return kj::strTree(encodeString(value.getString()));

      case JsonValue::ARRAY: {
        auto array = value.getArray();
        uint subIndent = indent + (array.size() > 1);
        bool childMultiline = false;
        auto encodedElements = KJ_MAP(element, array) {
          return encodeRaw(element, subIndent, childMultiline, false);
        };

        return kj::strTree('[', encodeList(
            kj::mv(encodedElements), childMultiline, indent, multiline, hasPrefix), ']');
      }

      case JsonValue::OBJECT: {
        auto object = value.getObject();
        uint subIndent = indent + (object.size() > 1);
        bool childMultiline = false;
        kj::StringPtr colon = prettyPrint ? ": " : ":";
        auto encodedElements = KJ_MAP(field, object) {
          return kj::strTree(
              encodeString(field.getName()), colon,
              encodeRaw(field.getValue(), subIndent, childMultiline, true));
        };

        return kj::strTree('{', encodeList(
            kj::mv(encodedElements), childMultiline, indent, multiline, hasPrefix), '}');
      }

      case JsonValue::CALL: {
        auto call = value.getCall();
        auto params = call.getParams();
        uint subIndent = indent + (params.size() > 1);
        bool childMultiline = false;
        auto encodedElements = KJ_MAP(element, params) {
          return encodeRaw(element, subIndent, childMultiline, false);
        };

        return kj::strTree(call.getFunction(), '(', encodeList(
            kj::mv(encodedElements), childMultiline, indent, multiline, true), ')');
      }

      case JsonValue::RAW: {
        return kj::strTree(value.getRaw());
      }
    }

    KJ_FAIL_ASSERT("unknown JsonValue type", static_cast<uint>(value.which()));
  }

  kj::String encodeString(kj::StringPtr chars) const {
    static const char HEXDIGITS[] = "0123456789abcdef";
    kj::Vector<char> escaped(chars.size() + 3);

    escaped.add('"');
    for (char c: chars) {
      switch (c) {
        case '\"': escaped.addAll(kj::StringPtr("\\\"")); break;
        case '\\': escaped.addAll(kj::StringPtr("\\\\")); break;
        case '\b': escaped.addAll(kj::StringPtr("\\b")); break;
        case '\f': escaped.addAll(kj::StringPtr("\\f")); break;
        case '\n': escaped.addAll(kj::StringPtr("\\n")); break;
        case '\r': escaped.addAll(kj::StringPtr("\\r")); break;
        case '\t': escaped.addAll(kj::StringPtr("\\t")); break;
        default:
          if (static_cast<uint8_t>(c) < 0x20) {
            escaped.addAll(kj::StringPtr("\\u00"));
            uint8_t c2 = c;
            escaped.add(HEXDIGITS[c2 / 16]);
            escaped.add(HEXDIGITS[c2 % 16]);
          } else {
            escaped.add(c);
          }
          break;
      }
    }
    escaped.add('"');
    escaped.add('\0');

    return kj::String(escaped.releaseAsArray());
  }

  kj::StringTree encodeList(kj::Array<kj::StringTree> elements,
                            bool hasMultilineElement, uint indent, bool& multiline,
                            bool hasPrefix) const {
    size_t maxChildSize = 0;
    for (auto& e: elements) maxChildSize = kj::max(maxChildSize, e.size());

    kj::StringPtr prefix;
    kj::StringPtr delim;
    kj::StringPtr suffix;
    kj::String ownPrefix;
    kj::String ownDelim;
    if (!prettyPrint) {
      // No whitespace.
      delim = ",";
      prefix = "";
      suffix = "";
    } else if ((elements.size() > 1) && (hasMultilineElement || maxChildSize > 50)) {
      // If the array contained any multi-line elements, OR it contained sufficiently long
      // elements, then put each element on its own line.
      auto indentSpace = kj::repeat(' ', (indent + 1) * 2);
      delim = ownDelim = kj::str(",\n", indentSpace);
      multiline = true;
      if (hasPrefix) {
        // We're producing a multi-line list, and the first line has some garbage in front of it.
        // Therefore, move the first element to the next line.
        prefix = ownPrefix = kj::str("\n", indentSpace);
      } else {
        prefix = " ";
      }
      suffix = " ";
    } else {
      // Put everything on one line, but add spacing between elements for legibility.
      delim = ", ";
      prefix = "";
      suffix = "";
    }

    return kj::strTree(prefix, kj::StringTree(kj::mv(elements), delim), suffix);
  }
};

JsonCodec::JsonCodec()
    : impl(kj::heap<Impl>()) {}
JsonCodec::~JsonCodec() noexcept(false) {}

void JsonCodec::setPrettyPrint(bool enabled) { impl->prettyPrint = enabled; }

void JsonCodec::setMaxNestingDepth(size_t maxNestingDepth) {
  impl->maxNestingDepth = maxNestingDepth;
}

void JsonCodec::setHasMode(HasMode mode) { impl->hasMode = mode; }

void JsonCodec::setRejectUnknownFields(bool enabled) { impl->rejectUnknownFields = enabled; }

kj::String JsonCodec::encode(DynamicValue::Reader value, Type type) const {
  MallocMessageBuilder message;
  auto json = message.getRoot<JsonValue>();
  encode(value, type, json);
  return encodeRaw(json);
}

void JsonCodec::decode(kj::ArrayPtr<const char> input, DynamicStruct::Builder output) const {
  MallocMessageBuilder message;
  auto json = message.getRoot<JsonValue>();
  decodeRaw(input, json);
  decode(json, output);
}

Orphan<DynamicValue> JsonCodec::decode(
    kj::ArrayPtr<const char> input, Type type, Orphanage orphanage) const {
  MallocMessageBuilder message;
  auto json = message.getRoot<JsonValue>();
  decodeRaw(input, json);
  return decode(json, type, orphanage);
}

kj::String JsonCodec::encodeRaw(JsonValue::Reader value) const {
  bool multiline = false;
  return impl->encodeRaw(value, 0, multiline, false).flatten();
}

void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const {
  // TODO(someday): For interfaces, check for handlers on superclasses, per documentation...
  // TODO(someday): For branded types, should we check for handlers on the generic?
  // TODO(someday): Allow registering handlers for "all structs", "all lists", etc?
  KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
    (*handler)->encodeBase(*this, input, output);
    return;
  }

  switch (type.which()) {
    case schema::Type::VOID:
      output.setNull();
      break;
    case schema::Type::BOOL:
      output.setBoolean(input.as<bool>());
      break;
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
      output.setNumber(input.as<double>());
      break;
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
      {
        double value = input.as<double>();
        // Inf, -inf and NaN are not allowed in the JSON spec. Storing into string.
        if (kj::inf() == value) {
          output.setString("Infinity");
        } else if (-kj::inf() == value) {
          output.setString("-Infinity");
        } else if (kj::isNaN(value)) {
          output.setString("NaN");
        } else {
          output.setNumber(value);
        }
      }
      break;
    case schema::Type::INT64:
      output.setString(kj::str(input.as<int64_t>()));
      break;
    case schema::Type::UINT64:
      output.setString(kj::str(input.as<uint64_t>()));
      break;
    case schema::Type::TEXT:
      output.setString(kj::str(input.as<Text>()));
      break;
    case schema::Type::DATA: {
      // Turn into array of byte values. Yep, this is pretty ugly. People really need to override
      // this with a handler.
      auto bytes = input.as<Data>();
      auto array = output.initArray(bytes.size());
      for (auto i: kj::indices(bytes)) {
        array[i].setNumber(bytes[i]);
      }
      break;
    }
    case schema::Type::LIST: {
      auto list = input.as<DynamicList>();
      auto elementType = type.asList().getElementType();
      auto array = output.initArray(list.size());
      for (auto i: kj::indices(list)) {
        encode(list[i], elementType, array[i]);
      }
      break;
    }
    case schema::Type::ENUM: {
      auto e = input.as<DynamicEnum>();
      KJ_IF_MAYBE(symbol, e.getEnumerant()) {
        output.setString(symbol->getProto().getName());
      } else {
        output.setNumber(e.getRaw());
      }
      break;
    }
    case schema::Type::STRUCT: {
      auto structValue = input.as<capnp::DynamicStruct>();
      auto nonUnionFields = structValue.getSchema().getNonUnionFields();

      KJ_STACK_ARRAY(bool, hasField, nonUnionFields.size(), 32, 128);

      uint fieldCount = 0;
      for (auto i: kj::indices(nonUnionFields)) {
        fieldCount += (hasField[i] = structValue.has(nonUnionFields[i], impl->hasMode));
      }

      // We try to write the union field, if any, in proper order with the rest.
      auto which = structValue.which();
      bool unionFieldIsNull = false;

      KJ_IF_MAYBE(field, which) {
        // Even if the union field is null, if it is not the default field of the union then we
        // have to print it anyway.
        unionFieldIsNull = !structValue.has(*field, impl->hasMode);
        if (field->getProto().getDiscriminantValue() != 0 || !unionFieldIsNull) {
          ++fieldCount;
        } else {
          which = nullptr;
        }
      }

      auto object = output.initObject(fieldCount);

      size_t pos = 0;
      for (auto i: kj::indices(nonUnionFields)) {
        auto field = nonUnionFields[i];
        KJ_IF_MAYBE(unionField, which) {
          if (unionField->getIndex() < field.getIndex()) {
            auto outField = object[pos++];
            outField.setName(unionField->getProto().getName());
            if (unionFieldIsNull) {
              outField.initValue().setNull();
            } else {
              encodeField(*unionField, structValue.get(*unionField), outField.initValue());
            }
            which = nullptr;
          }
        }
        if (hasField[i]) {
          auto outField = object[pos++];
          outField.setName(field.getProto().getName());
          encodeField(field, structValue.get(field), outField.initValue());
        }
      }
      if (which != nullptr) {
        // Union field not printed yet; must be last.
        auto unionField = KJ_ASSERT_NONNULL(which);
        auto outField = object[pos++];
        outField.setName(unionField.getProto().getName());
        if (unionFieldIsNull) {
          outField.initValue().setNull();
        } else {
          encodeField(unionField, structValue.get(unionField), outField.initValue());
        }
      }
      KJ_ASSERT(pos == fieldCount);
      break;
    }
    case schema::Type::INTERFACE:
      KJ_FAIL_REQUIRE("don't know how to JSON-encode capabilities; "
                      "please register a JsonCodec::Handler for this");
    case schema::Type::ANY_POINTER:
      KJ_FAIL_REQUIRE("don't know how to JSON-encode AnyPointer; "
                      "please register a JsonCodec::Handler for this");
  }
}

void JsonCodec::encodeField(StructSchema::Field field, DynamicValue::Reader input,
                            JsonValue::Builder output) const {
  KJ_IF_MAYBE(handler, impl->fieldHandlers.find(field)) {
    (*handler)->encodeBase(*this, input, output);
    return;
  }

  encode(input, field.getType(), output);
}

Orphan<DynamicList> JsonCodec::decodeArray(List<JsonValue>::Reader input, ListSchema type, Orphanage orphanage) const {
  auto orphan = orphanage.newOrphan(type, input.size());
  auto output = orphan.get();
  for (auto i: kj::indices(input)) {
    output.adopt(i, decode(input[i], type.getElementType(), orphanage));
  }
  return orphan;
}

void JsonCodec::decodeObject(JsonValue::Reader input, StructSchema type, Orphanage orphanage, DynamicStruct::Builder output) const {
  KJ_REQUIRE(input.isObject(), "Expected object value") { return; }
  for (auto field: input.getObject()) {
    KJ_IF_MAYBE(fieldSchema, type.findFieldByName(field.getName())) {
      decodeField(*fieldSchema, field.getValue(), orphanage, output);
    } else {
      KJ_REQUIRE(!impl->rejectUnknownFields, "Unknown field", field.getName());
    }
  }
}

void JsonCodec::decodeField(StructSchema::Field fieldSchema, JsonValue::Reader fieldValue,
                            Orphanage orphanage, DynamicStruct::Builder output) const {
  auto fieldType = fieldSchema.getType();

  KJ_IF_MAYBE(handler, impl->fieldHandlers.find(fieldSchema)) {
    output.adopt(fieldSchema, (*handler)->decodeBase(*this, fieldValue, fieldType, orphanage));
  } else {
    output.adopt(fieldSchema, decode(fieldValue, fieldType, orphanage));
  }
}

void JsonCodec::decode(JsonValue::Reader input, DynamicStruct::Builder output) const {
  auto type = output.getSchema();

  KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
    return (*handler)->decodeStructBase(*this, input, output);
  }

  decodeObject(input, type, Orphanage::getForMessageContaining(output), output);
}

Orphan<DynamicValue> JsonCodec::decode(
    JsonValue::Reader input, Type type, Orphanage orphanage) const {
  KJ_IF_MAYBE(handler, impl->typeHandlers.find(type)) {
    return (*handler)->decodeBase(*this, input, type, orphanage);
  }

  switch(type.which()) {
    case schema::Type::VOID:
      return capnp::VOID;
    case schema::Type::BOOL:
      switch (input.which()) {
        case JsonValue::BOOLEAN:
          return input.getBoolean();
        default:
          KJ_FAIL_REQUIRE("Expected boolean value");
      }
    case schema::Type::INT8:
    case schema::Type::INT16:
    case schema::Type::INT32:
    case schema::Type::INT64:
      // Relies on range check in DynamicValue::Reader::as<IntType>
      switch (input.which()) {
        case JsonValue::NUMBER:
          return input.getNumber();
        case JsonValue::STRING:
          return input.getString().parseAs<int64_t>();
        default:
          KJ_FAIL_REQUIRE("Expected integer value");
      }
    case schema::Type::UINT8:
    case schema::Type::UINT16:
    case schema::Type::UINT32:
    case schema::Type::UINT64:
      // Relies on range check in DynamicValue::Reader::as<IntType>
      switch (input.which()) {
        case JsonValue::NUMBER:
          return input.getNumber();
        case JsonValue::STRING:
          return input.getString().parseAs<uint64_t>();
        default:
          KJ_FAIL_REQUIRE("Expected integer value");
      }
    case schema::Type::FLOAT32:
    case schema::Type::FLOAT64:
      switch (input.which()) {
        case JsonValue::NULL_:
          return kj::nan();
        case JsonValue::NUMBER:
          return input.getNumber();
        case JsonValue::STRING:
          return input.getString().parseAs<double>();
        default:
          KJ_FAIL_REQUIRE("Expected float value");
      }
    case schema::Type::TEXT:
      switch (input.which()) {
        case JsonValue::STRING:
          return orphanage.newOrphanCopy(input.getString());
        default:
          KJ_FAIL_REQUIRE("Expected text value");
      }
    case schema::Type::DATA:
      switch (input.which()) {
        case JsonValue::ARRAY: {
          auto array = input.getArray();
          auto orphan = orphanage.newOrphan<Data>(array.size());
          auto data = orphan.get();
          for (auto i: kj::indices(array)) {
            auto x = array[i].getNumber();
            KJ_REQUIRE(byte(x) == x, "Number in byte array is not an integer in [0, 255]");
            data[i] = x;
          }
          return kj::mv(orphan);
        }
        default:
          KJ_FAIL_REQUIRE("Expected data value");
      }
    case schema::Type::LIST:
      switch (input.which()) {
        case JsonValue::ARRAY:
          return decodeArray(input.getArray(), type.asList(), orphanage);
        default:
          KJ_FAIL_REQUIRE("Expected list value") { break; }
          return orphanage.newOrphan(type.asList(), 0);
      }
    case schema::Type::ENUM:
      switch (input.which()) {
        case JsonValue::STRING:
          return DynamicEnum(type.asEnum().getEnumerantByName(input.getString()));
        default:
          KJ_FAIL_REQUIRE("Expected enum value") { break; }
          return DynamicEnum(type.asEnum(), 0);
      }
    case schema::Type::STRUCT: {
      auto structType = type.asStruct();
      auto orphan = orphanage.newOrphan(structType);
      decodeObject(input, structType, orphanage, orphan.get());
      return kj::mv(orphan);
    }
    case schema::Type::INTERFACE:
      KJ_FAIL_REQUIRE("don't know how to JSON-decode capabilities; "
                      "please register a JsonCodec::Handler for this");
    case schema::Type::ANY_POINTER:
      KJ_FAIL_REQUIRE("don't know how to JSON-decode AnyPointer; "
                      "please register a JsonCodec::Handler for this");
  }

  KJ_CLANG_KNOWS_THIS_IS_UNREACHABLE_BUT_GCC_DOESNT;
}

// -----------------------------------------------------------------------------

namespace {

class Input {
public:
  Input(kj::ArrayPtr<const char> input) : wrapped(input) {}

  bool exhausted() {
    return wrapped.size() == 0 || wrapped.front() == '\0';
  }

  char nextChar() {
    KJ_REQUIRE(!exhausted(), "JSON message ends prematurely.");
    return wrapped.front();
  }

  void advance(size_t numBytes = 1) {
    KJ_REQUIRE(numBytes <= wrapped.size(), "JSON message ends prematurely.");
    wrapped = kj::arrayPtr(wrapped.begin() + numBytes, wrapped.end());
  }

  void advanceTo(const char *newPos) {
    KJ_REQUIRE(wrapped.begin() <= newPos && newPos < wrapped.end(),
        "JSON message ends prematurely.");
    wrapped = kj::arrayPtr(newPos, wrapped.end());
  }

  kj::ArrayPtr<const char> consume(size_t numBytes = 1) {
    auto originalPos = wrapped.begin();
    advance(numBytes);

    return kj::arrayPtr(originalPos, wrapped.begin());
  }

  void consume(char expected) {
    char current = nextChar();
    KJ_REQUIRE(current == expected, "Unexpected input in JSON message.");

    advance();
  }

  void consume(kj::ArrayPtr<const char> expected) {
    KJ_REQUIRE(wrapped.size() >= expected.size());

    auto prefix = wrapped.slice(0, expected.size());
    KJ_REQUIRE(prefix == expected, "Unexpected input in JSON message.");

    advance(expected.size());
  }

  bool tryConsume(char expected) {
    bool found = !exhausted() && nextChar() == expected;
    if (found) { advance(); }

    return found;
  }

  template <typename Predicate>
  void consumeOne(Predicate&& predicate) {
    char current = nextChar();
    KJ_REQUIRE(predicate(current), "Unexpected input in JSON message.");

    advance();
  }

  template <typename Predicate>
  kj::ArrayPtr<const char> consumeWhile(Predicate&& predicate) {
    auto originalPos = wrapped.begin();
    while (!exhausted() && predicate(nextChar())) { advance(); }

    return kj::arrayPtr(originalPos, wrapped.begin());
  }

  template <typename F>  // Function<void(Input&)>
  kj::ArrayPtr<const char> consumeCustom(F&& f) {
    // Allows consuming in a custom manner without exposing the wrapped ArrayPtr.
    auto originalPos = wrapped.begin();
    f(*this);

    return kj::arrayPtr(originalPos, wrapped.begin());
  }

  void consumeWhitespace() {
    consumeWhile([](char chr) {
      return (
        chr == ' '  ||
        chr == '\n' ||
        chr == '\r' ||
        chr == '\t'
      );
    });
  }


private:
  kj::ArrayPtr<const char> wrapped;

};  // class Input

class Parser {
public:
  Parser(size_t maxNestingDepth, kj::ArrayPtr<const char> input) :
    maxNestingDepth(maxNestingDepth), input(input), nestingDepth(0) {}

  void parseValue(JsonValue::Builder& output) {
    input.consumeWhitespace();
    KJ_DEFER(input.consumeWhitespace());

    KJ_REQUIRE(!input.exhausted(), "JSON message ends prematurely.");

    switch (input.nextChar()) {
      case 'n': input.consume(kj::StringPtr("null"));  output.setNull();         break;
      case 'f': input.consume(kj::StringPtr("false")); output.setBoolean(false); break;
      case 't': input.consume(kj::StringPtr("true"));  output.setBoolean(true);  break;
      case '"': parseString(output); break;
      case '[': parseArray(output);  break;
      case '{': parseObject(output); break;
      case '-': case '0': case '1': case '2': case '3':
      case '4': case '5': case '6': case '7': case '8':
      case '9': parseNumber(output); break;
      default: KJ_FAIL_REQUIRE("Unexpected input in JSON message.");
    }
  }

  void parseNumber(JsonValue::Builder& output) {
    output.setNumber(consumeNumber().parseAs<double>());
  }

  void parseString(JsonValue::Builder& output) {
    output.setString(consumeQuotedString());
  }

  void parseArray(JsonValue::Builder& output) {
    // TODO(perf): Using orphans leaves holes in the message. It's expected
    // that a JsonValue is used for interop, and won't be sent or written as a
    // Cap'n Proto message.  This also applies to parseObject below.
    kj::Vector<Orphan<JsonValue>> values;
    auto orphanage = Orphanage::getForMessageContaining(output);
    bool expectComma = false;

    input.consume('[');
    KJ_REQUIRE(++nestingDepth <= maxNestingDepth, "JSON message nested too deeply.");
    KJ_DEFER(--nestingDepth);

    while (input.consumeWhitespace(), input.nextChar() != ']') {
      auto orphan = orphanage.newOrphan<JsonValue>();
      auto builder = orphan.get();

      if (expectComma) {
        input.consumeWhitespace();
        input.consume(',');
        input.consumeWhitespace();
      }

      parseValue(builder);
      values.add(kj::mv(orphan));

      expectComma = true;
    }

    output.initArray(values.size());
    auto array = output.getArray();

    for (auto i : kj::indices(values)) {
      array.adoptWithCaveats(i, kj::mv(values[i]));
    }

    input.consume(']');
  }

  void parseObject(JsonValue::Builder& output) {
    kj::Vector<Orphan<JsonValue::Field>> fields;
    auto orphanage = Orphanage::getForMessageContaining(output);
    bool expectComma = false;

    input.consume('{');
    KJ_REQUIRE(++nestingDepth <= maxNestingDepth, "JSON message nested too deeply.");
    KJ_DEFER(--nestingDepth);

    while (input.consumeWhitespace(), input.nextChar() != '}') {
      auto orphan = orphanage.newOrphan<JsonValue::Field>();
      auto builder = orphan.get();

      if (expectComma) {
        input.consumeWhitespace();
        input.consume(',');
        input.consumeWhitespace();
      }

      builder.setName(consumeQuotedString());

      input.consumeWhitespace();
      input.consume(':');
      input.consumeWhitespace();

      auto valueBuilder = builder.getValue();
      parseValue(valueBuilder);

      fields.add(kj::mv(orphan));

      expectComma = true;
    }

    output.initObject(fields.size());
    auto object = output.getObject();

    for (auto i : kj::indices(fields)) {
      object.adoptWithCaveats(i, kj::mv(fields[i]));
    }

    input.consume('}');
  }

  bool inputExhausted() { return input.exhausted(); }

private:
  kj::String consumeQuotedString() {
    input.consume('"');
    // TODO(perf): Avoid copy / alloc if no escapes encountered.
    // TODO(perf): Get statistics on string size and preallocate?
    kj::Vector<char> decoded;

    do {
      auto stringValue = input.consumeWhile([](const char chr) {
          return chr != '"' && chr != '\\';
      });

      decoded.addAll(stringValue);

      if (input.nextChar() == '\\') {  // handle escapes.
        input.advance();
        switch(input.nextChar()) {
          case '"' : decoded.add('"' ); input.advance(); break;
          case '\\': decoded.add('\\'); input.advance(); break;
          case '/' : decoded.add('/' ); input.advance(); break;
          case 'b' : decoded.add('\b'); input.advance(); break;
          case 'f' : decoded.add('\f'); input.advance(); break;
          case 'n' : decoded.add('\n'); input.advance(); break;
          case 'r' : decoded.add('\r'); input.advance(); break;
          case 't' : decoded.add('\t'); input.advance(); break;
          case 'u' :
            input.consume('u');
            unescapeAndAppend(input.consume(size_t(4)), decoded);
            break;
          default: KJ_FAIL_REQUIRE("Invalid escape in JSON string."); break;
        }
      }

    } while(input.nextChar() != '"');

    input.consume('"');
    decoded.add('\0');

    // TODO(perf): This copy can be eliminated, but I can't find the kj::wayToDoIt();
    return kj::String(decoded.releaseAsArray());
  }

  kj::String consumeNumber() {
    auto numArrayPtr = input.consumeCustom([](Input& input) {
      input.tryConsume('-');
      if (!input.tryConsume('0')) {
        input.consumeOne([](char c) { return '1' <= c && c <= '9'; });
        input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
      }

      if (input.tryConsume('.')) {
        input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
      }

      if (input.tryConsume('e') || input.tryConsume('E')) {
        input.tryConsume('+') || input.tryConsume('-');
        input.consumeWhile([](char c) { return '0' <= c && c <= '9'; });
      }
    });

    KJ_REQUIRE(numArrayPtr.size() > 0, "Expected number in JSON input.");

    kj::Vector<char> number;
    number.addAll(numArrayPtr);
    number.add('\0');

    return kj::String(number.releaseAsArray());
  }

  // TODO(someday): This "interface" is ugly, and won't work if/when surrogates are handled.
  void unescapeAndAppend(kj::ArrayPtr<const char> hex, kj::Vector<char>& target) {
    KJ_REQUIRE(hex.size() == 4);
    int codePoint = 0;

    for (int i = 0; i < 4; ++i) {
      char c = hex[i];
      codePoint <<= 4;

      if ('0' <= c && c <= '9') {
        codePoint |= c - '0';
      } else if ('a' <= c && c <= 'f') {
        codePoint |= c - 'a' + 10;
      } else if ('A' <= c && c <= 'F') {
        codePoint |= c - 'A' + 10;
      } else {
        KJ_FAIL_REQUIRE("Invalid hex digit in unicode escape.", c);
      }
    }

    if (codePoint < 128) {
      target.add(0x7f & static_cast<char>(codePoint));
    } else {
      // TODO(perf): This is sorta malloc-heavy...
      char16_t u = codePoint;
      target.addAll(kj::decodeUtf16(kj::arrayPtr(&u, 1)));
    }
  }

  const size_t maxNestingDepth;
  Input input;
  size_t nestingDepth;


};  // class Parser

}  // namespace


void JsonCodec::decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const {
  Parser parser(impl->maxNestingDepth, input);
  parser.parseValue(output);

  KJ_REQUIRE(parser.inputExhausted(), "Input remains after parsing JSON.");
}

// -----------------------------------------------------------------------------

Orphan<DynamicValue> JsonCodec::HandlerBase::decodeBase(
    const JsonCodec& codec, JsonValue::Reader input, Type type, Orphanage orphanage) const {
  KJ_FAIL_ASSERT("JSON decoder handler type / value type mismatch");
}
void JsonCodec::HandlerBase::decodeStructBase(
    const JsonCodec& codec, JsonValue::Reader input, DynamicStruct::Builder output) const {
  KJ_FAIL_ASSERT("JSON decoder handler type / value type mismatch");
}

void JsonCodec::addTypeHandlerImpl(Type type, HandlerBase& handler) {
  impl->typeHandlers.upsert(type, &handler, [](HandlerBase*& existing, HandlerBase* replacement) {
    KJ_REQUIRE(existing == replacement, "type already has a different registered handler");
  });
}

void JsonCodec::addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler) {
  KJ_REQUIRE(type == field.getType(),
      "handler type did not match field type for addFieldHandler()");
  impl->fieldHandlers.upsert(field, &handler, [](HandlerBase*& existing, HandlerBase* replacement) {
    KJ_REQUIRE(existing == replacement, "field already has a different registered handler");
  });
}

// =======================================================================================

static constexpr uint64_t JSON_NAME_ANNOTATION_ID = 0xfa5b1fd61c2e7c3dull;
static constexpr uint64_t JSON_FLATTEN_ANNOTATION_ID = 0x82d3e852af0336bfull;
static constexpr uint64_t JSON_DISCRIMINATOR_ANNOTATION_ID = 0xcfa794e8d19a0162ull;
static constexpr uint64_t JSON_BASE64_ANNOTATION_ID = 0xd7d879450a253e4bull;
static constexpr uint64_t JSON_HEX_ANNOTATION_ID = 0xf061e22f0ae5c7b5ull;

class JsonCodec::Base64Handler final: public JsonCodec::Handler<capnp::Data> {
public:
  void encode(const JsonCodec& codec, capnp::Data::Reader input, JsonValue::Builder output) const {
    output.setString(kj::encodeBase64(input));
  }

  Orphan<capnp::Data> decode(const JsonCodec& codec, JsonValue::Reader input,
                             Orphanage orphanage) const {
    return orphanage.newOrphanCopy(capnp::Data::Reader(kj::decodeBase64(input.getString())));
  }
};

class JsonCodec::HexHandler final: public JsonCodec::Handler<capnp::Data> {
public:
  void encode(const JsonCodec& codec, capnp::Data::Reader input, JsonValue::Builder output) const {
    output.setString(kj::encodeHex(input));
  }

  Orphan<capnp::Data> decode(const JsonCodec& codec, JsonValue::Reader input,
                             Orphanage orphanage) const {
    return orphanage.newOrphanCopy(capnp::Data::Reader(kj::decodeHex(input.getString())));
  }
};

class JsonCodec::AnnotatedHandler final: public JsonCodec::Handler<DynamicStruct> {
public:
  AnnotatedHandler(JsonCodec& codec, StructSchema schema,
                   kj::Maybe<json::DiscriminatorOptions::Reader> discriminator,
                   kj::Maybe<kj::StringPtr> unionDeclName,
                   kj::Vector<Schema>& dependencies)
      : schema(schema) {
    auto schemaProto = schema.getProto();
    auto typeName = schemaProto.getDisplayName();

    if (discriminator == nullptr) {
      // There are two cases of unions:
      // * Named unions, which are special cases of named groups. In this case, the union may be
      //   annotated by annotating the field. In this case, we receive a non-null `discriminator`
      //   as a constructor parameter, and schemaProto.getAnnotations() must be empty because
      //   it's not possible to annotate a group's type (because the type is anonymous).
      // * Unnamed unions, of which there can only be one in any particular scope. In this case,
      //   the parent struct type itself is annotated.
      // So if we received `null` as the constructor parameter, check for annotations on the struct
      // type.
      for (auto anno: schemaProto.getAnnotations()) {
        switch (anno.getId()) {
          case JSON_DISCRIMINATOR_ANNOTATION_ID:
            discriminator = anno.getValue().getStruct().getAs<json::DiscriminatorOptions>();
            break;
        }
      }
    }

    KJ_IF_MAYBE(d, discriminator) {
      if (d->hasName()) {
        unionTagName = d->getName();
      } else {
        unionTagName = unionDeclName;
      }
      KJ_IF_MAYBE(u, unionTagName) {
        fieldsByName.insert(*u, FieldNameInfo {
          FieldNameInfo::UNION_TAG, 0, 0, nullptr
        });
      }

      if (d->hasValueName()) {
        fieldsByName.insert(d->getValueName(), FieldNameInfo {
          FieldNameInfo::UNION_VALUE, 0, 0, nullptr
        });
      }
    }

    discriminantOffset = schemaProto.getStruct().getDiscriminantOffset();

    fields = KJ_MAP(field, schema.getFields()) {
      auto fieldProto = field.getProto();
      auto type = field.getType();
      auto fieldName = fieldProto.getName();

      FieldNameInfo nameInfo;
      nameInfo.index = field.getIndex();
      nameInfo.type = FieldNameInfo::NORMAL;
      nameInfo.prefixLength = 0;

      FieldInfo info;
      info.name = fieldName;

      kj::Maybe<json::DiscriminatorOptions::Reader> subDiscriminator;
      bool flattened = false;
      for (auto anno: field.getProto().getAnnotations()) {
        switch (anno.getId()) {
          case JSON_NAME_ANNOTATION_ID:
            info.name = anno.getValue().getText();
            break;
          case JSON_FLATTEN_ANNOTATION_ID:
            KJ_REQUIRE(type.isStruct(), "only struct types can be flattened", fieldName, typeName);
            flattened = true;
            info.prefix = anno.getValue().getStruct().getAs<json::FlattenOptions>().getPrefix();
            break;
          case JSON_DISCRIMINATOR_ANNOTATION_ID:
            KJ_REQUIRE(fieldProto.isGroup(), "only unions can have discriminator");
            subDiscriminator = anno.getValue().getStruct().getAs<json::DiscriminatorOptions>();
            break;
          case JSON_BASE64_ANNOTATION_ID: {
            KJ_REQUIRE(field.getType().isData(), "only Data can be marked for base64 encoding");
            static Base64Handler handler;
            codec.addFieldHandler(field, handler);
            break;
          }
          case JSON_HEX_ANNOTATION_ID: {
            KJ_REQUIRE(field.getType().isData(), "only Data can be marked for hex encoding");
            static HexHandler handler;
            codec.addFieldHandler(field, handler);
            break;
          }
        }
      }

      if (fieldProto.isGroup()) {
        // Load group type handler now, even if not flattened, so that we can pass its
        // `subDiscriminator`.
        kj::Maybe<kj::StringPtr> subFieldName;
        if (flattened) {
          // If the group was flattened, then we allow its field name to be used as the
          // discriminator name, so that the discriminator doesn't have to explicitly specify a
          // name.
          subFieldName = fieldName;
        }
        auto& subHandler = codec.loadAnnotatedHandler(
            type.asStruct(), subDiscriminator, subFieldName, dependencies);
        if (flattened) {
          info.flattenHandler = subHandler;
        }
      } else if (type.isStruct()) {
        if (flattened) {
          info.flattenHandler = codec.loadAnnotatedHandler(
              type.asStruct(), nullptr, nullptr, dependencies);
        }
      }

      bool isUnionMember = fieldProto.getDiscriminantValue() != schema::Field::NO_DISCRIMINANT;

      KJ_IF_MAYBE(fh, info.flattenHandler) {
        // Set up fieldsByName for each of the child's fields.
        for (auto& entry: fh->fieldsByName) {
          kj::StringPtr flattenedName;
          kj::String ownName;
          if (info.prefix.size() > 0) {
            ownName = kj::str(info.prefix, entry.key);
            flattenedName = ownName;
          } else {
            flattenedName = entry.key;
          }

          fieldsByName.upsert(flattenedName, FieldNameInfo {
            isUnionMember ? FieldNameInfo::FLATTENED_FROM_UNION : FieldNameInfo::FLATTENED,
            field.getIndex(), (uint)info.prefix.size(), kj::mv(ownName)
          }, [&](FieldNameInfo& existing, FieldNameInfo&& replacement) {
            KJ_REQUIRE(existing.type == FieldNameInfo::FLATTENED_FROM_UNION &&
                       replacement.type == FieldNameInfo::FLATTENED_FROM_UNION,
                "flattened members have the same name and are not mutually exclusive");
          });
        }
      }

      info.nameForDiscriminant = info.name;

      if (!flattened) {
        bool isUnionWithValueName = false;
        if (isUnionMember) {
          KJ_IF_MAYBE(d, discriminator) {
            if (d->hasValueName()) {
              info.name = d->getValueName();
              isUnionWithValueName = true;
            }
          }
        }

        if (!isUnionWithValueName) {
          fieldsByName.insert(info.name, kj::mv(nameInfo));
        }
      }

      if (isUnionMember) {
        unionTagValues.insert(info.nameForDiscriminant, field);
      }

      // Look for dependencies that we need to add.
      while (type.isList()) type = type.asList().getElementType();
      if (codec.impl->typeHandlers.find(type) == nullptr) {
        switch (type.which()) {
          case schema::Type::STRUCT:
            dependencies.add(type.asStruct());
            break;
          case schema::Type::ENUM:
            dependencies.add(type.asEnum());
            break;
          case schema::Type::INTERFACE:
            dependencies.add(type.asInterface());
            break;
          default:
            break;
        }
      }

      return info;
    };
  }

  const StructSchema schema;

  void encode(const JsonCodec& codec, DynamicStruct::Reader input,
              JsonValue::Builder output) const override {
    kj::Vector<FlattenedField> flattenedFields;
    gatherForEncode(codec, input, nullptr, nullptr, flattenedFields);

    auto outs = output.initObject(flattenedFields.size());
    for (auto i: kj::indices(flattenedFields)) {
      auto& in = flattenedFields[i];
      auto out = outs[i];
      out.setName(in.name);
      KJ_SWITCH_ONEOF(in.type) {
        KJ_CASE_ONEOF(type, Type) {
          codec.encode(in.value, type, out.initValue());
        }
        KJ_CASE_ONEOF(field, StructSchema::Field) {
          codec.encodeField(field, in.value, out.initValue());
        }
      }
    }
  }

  void decode(const JsonCodec& codec, JsonValue::Reader input,
              DynamicStruct::Builder output) const override {
    KJ_REQUIRE(input.isObject());
    kj::HashSet<const void*> unionsSeen;
    kj::Vector<JsonValue::Field::Reader> retries;
    for (auto field: input.getObject()) {
      if (!decodeField(codec, field.getName(), field.getValue(), output, unionsSeen)) {
        retries.add(field);
      }
    }
    while (!retries.empty()) {
      auto retriesCopy = kj::mv(retries);
      KJ_ASSERT(retries.empty());
      for (auto field: retriesCopy) {
        if (!decodeField(codec, field.getName(), field.getValue(), output, unionsSeen)) {
          retries.add(field);
        }
      }
      if (retries.size() == retriesCopy.size()) {
        // We made no progress in this iteration. Give up on the remaining fields.
        break;
      }
    }
  }

private:
  struct FieldInfo {
    kj::StringPtr name;
    kj::StringPtr nameForDiscriminant;
    kj::Maybe<const AnnotatedHandler&> flattenHandler;
    kj::StringPtr prefix;
  };

  kj::Array<FieldInfo> fields;
  // Maps field index -> info about the field

  struct FieldNameInfo {
    enum {
      NORMAL,
      // This is a normal field with the given `index`.

      FLATTENED,
      // This is a field of a flattened inner struct or group (that is not in a union). `index`
      // is the field index of the particular struct/group field.

      UNION_TAG,
      // The parent struct is a flattened union, and this field is the discriminant tag. It is a
      // string field whose name determines the union type. `index` is not used.

      FLATTENED_FROM_UNION,
      // The parent struct is a flattened union, and some of the union's members are flattened
      // structs or groups, and this field is possibly a member of one or more of them. `index`
      // is not used, because it's possible that the same field name appears in multiple variants.
      // Instead, the parser must find the union tag, and then can descend and attempt to parse
      // the field in the context of whichever variant is selected.

      UNION_VALUE
      // This field is the value of a discriminated union that has `valueName` set.
    } type;

    uint index;
    // For `NORMAL` and `FLATTENED`, the index of the field in schema.getFields().

    uint prefixLength;
    kj::String ownName;
  };

  kj::HashMap<kj::StringPtr, FieldNameInfo> fieldsByName;
  // Maps JSON names to info needed to parse them.

  kj::HashMap<kj::StringPtr, StructSchema::Field> unionTagValues;
  // If the parent struct is a flattened union, it has a tag field which is a string with one of
  // these values. The map maps to the union member to set.

  kj::Maybe<kj::StringPtr> unionTagName;
  // If the parent struct is a flattened union, the name of the "tag" field.

  uint discriminantOffset;
  // Shortcut for schema.getProto().getStruct().getDiscriminantOffset(), used in a hack to identify
  // which unions have been seen.

  struct FlattenedField {
    kj::String ownName;
    kj::StringPtr name;
    kj::OneOf<StructSchema::Field, Type> type;
    DynamicValue::Reader value;

    FlattenedField(kj::StringPtr prefix, kj::StringPtr name,
                   kj::OneOf<StructSchema::Field, Type> type, DynamicValue::Reader value)
        : ownName(prefix.size() > 0 ? kj::str(prefix, name) : nullptr),
          name(prefix.size() > 0 ? ownName : name),
          type(type), value(value) {}
  };

  void gatherForEncode(const JsonCodec& codec, DynamicValue::Reader input,
                       kj::StringPtr prefix, kj::StringPtr morePrefix,
                       kj::Vector<FlattenedField>& flattenedFields) const {
    kj::String ownPrefix;
    if (morePrefix.size() > 0) {
      if (prefix.size() > 0) {
        ownPrefix = kj::str(prefix, morePrefix);
        prefix = ownPrefix;
      } else {
        prefix = morePrefix;
      }
    }

    auto reader = input.as<DynamicStruct>();
    auto schema = reader.getSchema();
    for (auto field: schema.getNonUnionFields()) {
      auto& info = fields[field.getIndex()];
      if (!reader.has(field, codec.impl->hasMode)) {
        // skip
      } else KJ_IF_MAYBE(handler, info.flattenHandler) {
        handler->gatherForEncode(codec, reader.get(field), prefix, info.prefix, flattenedFields);
      } else {
        flattenedFields.add(FlattenedField {
            prefix, info.name, field, reader.get(field) });
      }
    }

    KJ_IF_MAYBE(which, reader.which()) {
      auto& info = fields[which->getIndex()];
      KJ_IF_MAYBE(tag, unionTagName) {
        flattenedFields.add(FlattenedField {
            prefix, *tag, Type(schema::Type::TEXT), Text::Reader(info.nameForDiscriminant) });
      }

      KJ_IF_MAYBE(handler, info.flattenHandler) {
        handler->gatherForEncode(codec, reader.get(*which), prefix, info.prefix, flattenedFields);
      } else {
        auto type = which->getType();
        if (type.which() == schema::Type::VOID && unionTagName != nullptr) {
          // When we have an explicit union discriminant, we don't need to encode void fields.
        } else {
          flattenedFields.add(FlattenedField {
              prefix, info.name, *which, reader.get(*which) });
        }
      }
    }
  }

  bool decodeField(const JsonCodec& codec, kj::StringPtr name, JsonValue::Reader value,
                   DynamicStruct::Builder output, kj::HashSet<const void*>& unionsSeen) const {
    KJ_ASSERT(output.getSchema() == schema);

    KJ_IF_MAYBE(info, fieldsByName.find(name)) {
      switch (info->type) {
        case FieldNameInfo::NORMAL: {
          auto field = output.getSchema().getFields()[info->index];
          codec.decodeField(field, value, Orphanage::getForMessageContaining(output), output);
          return true;
        }
        case FieldNameInfo::FLATTENED:
          return KJ_ASSERT_NONNULL(fields[info->index].flattenHandler)
              .decodeField(codec, name.slice(info->prefixLength), value,
                  output.get(output.getSchema().getFields()[info->index]).as<DynamicStruct>(),
                  unionsSeen);
        case FieldNameInfo::UNION_TAG: {
          KJ_REQUIRE(value.isString(), "Expected string value.");

          // Mark that we've seen a union tag for this struct.
          const void* ptr = getUnionInstanceIdentifier(output);
          KJ_IF_MAYBE(field, unionTagValues.find(value.getString())) {
            // clear() has the side-effect of activating this member of the union, without
            // allocating any objects.
            output.clear(*field);
            unionsSeen.insert(ptr);
          }
          return true;
        }
        case FieldNameInfo::FLATTENED_FROM_UNION: {
          const void* ptr = getUnionInstanceIdentifier(output);
          if (unionsSeen.contains(ptr)) {
            auto variant = KJ_ASSERT_NONNULL(output.which());
            return KJ_ASSERT_NONNULL(fields[variant.getIndex()].flattenHandler)
                .decodeField(codec, name.slice(info->prefixLength), value,
                    output.get(variant).as<DynamicStruct>(), unionsSeen);
          } else {
            // We haven't seen the union tag yet, so we can't parse this field yet. Try again later.
            return false;
          }
        }
        case FieldNameInfo::UNION_VALUE: {
          const void* ptr = getUnionInstanceIdentifier(output);
          if (unionsSeen.contains(ptr)) {
            auto variant = KJ_ASSERT_NONNULL(output.which());
            codec.decodeField(variant, value, Orphanage::getForMessageContaining(output), output);
            return true;
          } else {
            // We haven't seen the union tag yet, so we can't parse this field yet. Try again later.
            return false;
          }
        }
      }

      KJ_UNREACHABLE;
    } else {
      // Ignore undefined field -- unless the flag is set to reject them.
      KJ_REQUIRE(!codec.impl->rejectUnknownFields, "Unknown field", name);
      return true;
    }
  }

  const void* getUnionInstanceIdentifier(DynamicStruct::Builder obj) const {
    // Gets a value uniquely identifying an instance of a union.
    // HACK: We return a pointer to the union's discriminant within the underlying buffer.
    return reinterpret_cast<const uint16_t*>(
        AnyStruct::Reader(obj.asReader()).getDataSection().begin()) + discriminantOffset;
  }
};

class JsonCodec::AnnotatedEnumHandler final: public JsonCodec::Handler<DynamicEnum> {
public:
  AnnotatedEnumHandler(EnumSchema schema): schema(schema) {
    auto enumerants = schema.getEnumerants();
    auto builder = kj::heapArrayBuilder<kj::StringPtr>(enumerants.size());

    for (auto e: enumerants) {
      auto proto = e.getProto();
      kj::StringPtr name = proto.getName();

      for (auto anno: proto.getAnnotations()) {
        switch (anno.getId()) {
          case JSON_NAME_ANNOTATION_ID:
            name = anno.getValue().getText();
            break;
        }
      }

      builder.add(name);
      nameToValue.insert(name, e.getIndex());
    }

    valueToName = builder.finish();
  }

  void encode(const JsonCodec& codec, DynamicEnum input, JsonValue::Builder output) const override {
    KJ_IF_MAYBE(e, input.getEnumerant()) {
      KJ_ASSERT(e->getIndex() < valueToName.size());
      output.setString(valueToName[e->getIndex()]);
    } else {
      output.setNumber(input.getRaw());
    }
  }

  DynamicEnum decode(const JsonCodec& codec, JsonValue::Reader input) const override {
    if (input.isNumber()) {
      return DynamicEnum(schema, static_cast<uint16_t>(input.getNumber()));
    } else {
      uint16_t val = KJ_REQUIRE_NONNULL(nameToValue.find(input.getString()),
          "invalid enum value", input.getString());
      return DynamicEnum(schema.getEnumerants()[val]);
    }
  }

private:
  EnumSchema schema;
  kj::Array<kj::StringPtr> valueToName;
  kj::HashMap<kj::StringPtr, uint16_t> nameToValue;
};

class JsonCodec::JsonValueHandler final: public JsonCodec::Handler<DynamicStruct> {
public:
  void encode(const JsonCodec& codec, DynamicStruct::Reader input,
              JsonValue::Builder output) const override {
#if _MSC_VER && !defined(__clang__)
    // TODO(msvc): Hack to work around missing AnyStruct::Builder constructor on MSVC.
    rawCopy(input, toDynamic(output));
#else
    rawCopy(input, kj::mv(output));
#endif
  }

  void decode(const JsonCodec& codec, JsonValue::Reader input,
              DynamicStruct::Builder output) const override {
    rawCopy(input, kj::mv(output));
  }

private:
  void rawCopy(AnyStruct::Reader input, AnyStruct::Builder output) const {
    // HACK: Manually copy using AnyStruct, so that if JsonValue's definition changes, this code
    //   doesn't need to be updated. However, note that if JsonValue ever adds new fields that
    //   change its size, and the input struct is a newer version than the output, we may lose
    //   the new fields. Technically the "correct" thing to do would be to allocate the output
    //   struct to be exactly the same size as the input, but JsonCodec's Handler interface is
    //   not designed to allow that -- it passes in an already-allocated builder. Oops.
    auto dataIn = input.getDataSection();
    auto dataOut = output.getDataSection();
    memcpy(dataOut.begin(), dataIn.begin(), kj::min(dataOut.size(), dataIn.size()));

    auto ptrIn = input.getPointerSection();
    auto ptrOut = output.getPointerSection();
    for (auto i: kj::zeroTo(kj::min(ptrIn.size(), ptrOut.size()))) {
      ptrOut[i].set(ptrIn[i]);
    }
  }
};

JsonCodec::AnnotatedHandler& JsonCodec::loadAnnotatedHandler(
      StructSchema schema, kj::Maybe<json::DiscriminatorOptions::Reader> discriminator,
      kj::Maybe<kj::StringPtr> unionDeclName, kj::Vector<Schema>& dependencies) {
  auto& entry = impl->annotatedHandlers.upsert(schema, nullptr,
      [&](kj::Maybe<kj::Own<AnnotatedHandler>>& existing, auto dummy) {
    KJ_ASSERT(existing != nullptr,
        "cyclic JSON flattening detected", schema.getProto().getDisplayName());
  });

  KJ_IF_MAYBE(v, entry.value) {
    // Already exists.
    return **v;
  } else {
    // Not seen before.
    auto newHandler = kj::heap<AnnotatedHandler>(
          *this, schema, discriminator, unionDeclName, dependencies);
    auto& result = *newHandler;

    // Map may have changed, so we have to look up again.
    KJ_ASSERT_NONNULL(impl->annotatedHandlers.find(schema)) = kj::mv(newHandler);

    addTypeHandler(schema, result);
    return result;
  };
}

void JsonCodec::handleByAnnotation(Schema schema) {
  switch (schema.getProto().which()) {
    case schema::Node::STRUCT: {
      if (schema.getProto().getId() == capnp::typeId<JsonValue>()) {
        // Special handler for JsonValue.
        static JsonValueHandler GLOBAL_HANDLER;
        addTypeHandler(schema.asStruct(), GLOBAL_HANDLER);
      } else {
        kj::Vector<Schema> dependencies;
        loadAnnotatedHandler(schema.asStruct(), nullptr, nullptr, dependencies);
        for (auto dep: dependencies) {
          handleByAnnotation(dep);
        }
      }
      break;
    }
    case schema::Node::ENUM: {
      auto enumSchema = schema.asEnum();
      impl->annotatedEnumHandlers.findOrCreate(enumSchema, [&]() {
        auto handler = kj::heap<AnnotatedEnumHandler>(enumSchema);
        addTypeHandler(enumSchema, *handler);
        return kj::HashMap<Type, kj::Own<AnnotatedEnumHandler>>::Entry {
            enumSchema, kj::mv(handler) };
      });
      break;
    }
    default:
      break;
  }
}

} // namespace capnp
