int main(int argc, char const *argv[])
{
    std::cout << "opening values.json" << std::endl;
    std::ifstream ifs("values.json");
    if (!ifs) {
        TF_CODING_ERROR("Failed to open 'values.json' for reading");
        return 1;
    }

    // Set up expected values.
    std::cout << "parsing input stream" << std::endl;
    const JsValue value = JsParseStream(ifs);
    TF_AXIOM(value);
    TF_AXIOM(value.IsObject());

    std::cout << "unwrapping envelope" << std::endl;
    JsObject envelope = value.GetJsObject();
    TF_AXIOM(envelope["Object"].IsObject());
    JsObject object = envelope["Object"].GetJsObject();
    TF_AXIOM(!object.empty());

    // Convert the top-level value to another container type.
    std::cout << "converting container" << std::endl;
    const _Any result = JsConvertToContainerType<_Any, _Dictionary>(value);
    TF_AXIOM(!IsEmpty(result));
    TF_AXIOM(IsHolding<_Dictionary>(result));

    std::cout << "checking converted top-level object" << std::endl;
    const _Dictionary& dict = Get<_Dictionary>(result);
    _Dictionary::const_iterator i = dict.find("Object");
    TF_AXIOM(i != dict.end());
    TF_AXIOM(IsHolding<_Dictionary>(i->second));
    const _Dictionary& aObject = Get<_Dictionary>(i->second);

    std::cout << "checking converted values" << std::endl;

    for (const auto& p : aObject) {
        const std::type_info* ti = GetType(p.second);
        indent << "key " << p.first << " typeid is " <<
            (ti ? ArchGetDemangled(*ti) : "nil") << std::endl;

        IndenterScope scope(*indenter);

        if (p.first == "Array") {
            indent << "checking array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());

            // This array has heterogeneous values, so IsArrayOf<T> should
            // always return false.
            TF_AXIOM(!object[p.first].IsArrayOf<JsObject>());
            TF_AXIOM(!object[p.first].IsArrayOf<JsArray>());
            TF_AXIOM(!object[p.first].IsArrayOf<string>());
            TF_AXIOM(!object[p.first].IsArrayOf<double>());
            TF_AXIOM(!object[p.first].IsArrayOf<int>());
            TF_AXIOM(!object[p.first].IsArrayOf<int64_t>());
            TF_AXIOM(!object[p.first].IsArrayOf<uint64_t>());

        } else if (p.first == "ArrayString") {
            indent << "checking string array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(object[p.first].Is<JsArray>());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());
            _CheckArrayOf<string>(object[p.first]);
        } else if (p.first == "ArrayInt64") {
            indent << "checking int64 array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(object[p.first].Is<JsArray>());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());
            _CheckArrayOf<int64_t>(object[p.first]);
        } else if (p.first == "ArrayUInt64") {
            indent << "checking uint array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(object[p.first].Is<JsArray>());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());
            _CheckArrayOf<uint64_t>(object[p.first]);
        } else if (p.first == "ArrayReal") {
            indent << "checking real array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(object[p.first].Is<JsArray>());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());
            _CheckArrayOf<double>(object[p.first]);
        } else if (p.first == "ArrayBool") {
            indent << "checking bool array conversion" << std::endl;
            TF_AXIOM(object[p.first].IsArray());
            TF_AXIOM(object[p.first].Is<JsArray>());
            TF_AXIOM(IsHolding<_AnyVector>(p.second));
            _CheckArray(Get<_AnyVector>(p.second), object[p.first].GetJsArray());
            _CheckArray(Get<_AnyVector>(p.second), 
                        object[p.first].Get<JsArray>());
            _CheckArrayOf<bool>(object[p.first]);
        } else if (p.first == "String") {
            indent << "checking string conversion" << std::endl;
            TF_AXIOM(object[p.first].IsString());
            TF_AXIOM(object[p.first].Is<string>());
            TF_AXIOM(IsHolding<string>(p.second));
            TF_AXIOM(Get<string>(p.second) == object[p.first].GetString());
            TF_AXIOM(Get<string>(p.second) == object[p.first].Get<string>());
        } else if (p.first == "Int64") {
            indent << "checking int conversion" << std::endl;
            TF_AXIOM(object[p.first].IsInt());
            TF_AXIOM(object[p.first].Is<int64_t>());
            TF_AXIOM(IsHolding<int64_t>(p.second));
            TF_AXIOM(Get<int64_t>(p.second) == object[p.first].GetInt());
            TF_AXIOM(Get<int64_t>(p.second) == object[p.first].Get<int64_t>());
        } else if (p.first == "UInt64") {
            indent << "checking uint conversion" << std::endl;
            TF_AXIOM(object[p.first].IsInt());
            TF_AXIOM(object[p.first].Is<uint64_t>());
            TF_AXIOM(IsHolding<uint64_t>(p.second));
            TF_AXIOM(Get<uint64_t>(p.second) == static_cast<uint64_t>(object[p.first].GetInt()));
            TF_AXIOM(Get<uint64_t>(p.second) == object[p.first].Get<uint64_t>());
        } else if (p.first == "Real") {
            indent << "checking real conversion" << std::endl;
            TF_AXIOM(object[p.first].IsReal());
            TF_AXIOM(object[p.first].Is<double>());
            TF_AXIOM(IsHolding<double>(p.second));
            TF_AXIOM(Get<double>(p.second) == object[p.first].GetReal());
            TF_AXIOM(Get<double>(p.second) == object[p.first].Get<double>());
        } else if (p.first == "BoolTrue") {
            indent << "checking bool(true) conversion" << std::endl;
            TF_AXIOM(object[p.first].IsBool());
            TF_AXIOM(object[p.first].Is<bool>());
            TF_AXIOM(IsHolding<bool>(p.second));
            TF_AXIOM(Get<bool>(p.second));
            TF_AXIOM(object[p.first].Get<bool>());
        } else if (p.first == "BoolFalse") {
            indent << "checking bool(false) conversion" << std::endl;
            TF_AXIOM(object[p.first].IsBool());
            TF_AXIOM(object[p.first].Is<bool>());
            TF_AXIOM(IsHolding<bool>(p.second));
            TF_AXIOM(!Get<bool>(p.second));
            TF_AXIOM(!object[p.first].Get<bool>());
        } else if (p.first == "Null") {
            indent << "checking null conversion" << std::endl;
            TF_AXIOM(object[p.first].IsNull());
            TF_AXIOM(IsEmpty(p.second));
        }
    }

    std::cout << "PASSED" << std::endl;
    return 0;
}