diff --git a/modules/ANSFR/ANSFR.cpp b/modules/ANSFR/ANSFR.cpp index 6e2d049..34a03b3 100644 --- a/modules/ANSFR/ANSFR.cpp +++ b/modules/ANSFR/ANSFR.cpp @@ -1814,10 +1814,12 @@ namespace ANSCENTER { } } + // No faces is a valid state (user just created or faces already removed) if (faceIds.empty()) { LogThreadSafe("ANSFacialRecognition::DeleteFacesByUser", - "No faces found for user ID " + std::to_string(userId)); - return 0; + "No faces found for user ID " + std::to_string(userId) + " — skipping", + LogLevel::Debug); + return 1; } // Delete each face from database (lock per deletion) diff --git a/modules/ANSFR/ANSFRCommon.cpp b/modules/ANSFR/ANSFRCommon.cpp index e5e692c..fcf97b4 100644 --- a/modules/ANSFR/ANSFRCommon.cpp +++ b/modules/ANSFR/ANSFRCommon.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace ANSCENTER { constexpr size_t ndetections = 200; template T GetData(const boost::property_tree::ptree& pt, const std::string& key) @@ -684,46 +685,77 @@ namespace ANSCENTER { return faceResult; } - std::string ANSFRHelper::UserRecordToJsonString(const UserRecord userRecord) { - boost::property_tree::ptree root; - boost::property_tree::ptree faceIds; - for (int i = 0; i < userRecord.FaceIds.size(); i++) { - boost::property_tree::ptree faceIdNode; - faceIdNode.put("", userRecord.FaceIds[i]); - faceIds.push_back(std::make_pair("", faceIdNode)); + // Encode non-ASCII UTF-8 characters as double-escaped Unicode (\uXXXX) for JSON transport. + // Surrogate pairs are used for codepoints above U+FFFF. + static std::string DoubleEscapeUnicode(const std::string& utf8Str) { + bool hasNonAscii = false; + for (unsigned char c : utf8Str) { + if (c >= 0x80) { hasNonAscii = true; break; } } - root.put("user_id", userRecord.UserId); - root.put("user_code", userRecord.UserCode); - root.put("user_username", userRecord.UserName); - root.add_child("face_ids", faceIds); - std::ostringstream stream; - boost::property_tree::write_json(stream, root,false); - std::string userRecordResult = stream.str(); - return userRecordResult; + if (!hasNonAscii) return utf8Str; + std::string result; + result.reserve(utf8Str.size() * 2); + size_t i = 0; + while (i < utf8Str.size()) { + unsigned char c = static_cast(utf8Str[i]); + if (c < 0x80) { result += utf8Str[i++]; continue; } + uint32_t cp = 0; + if ((c & 0xE0) == 0xC0 && i + 1 < utf8Str.size()) { + cp = ((c & 0x1F) << 6) | (static_cast(utf8Str[i + 1]) & 0x3F); i += 2; + } else if ((c & 0xF0) == 0xE0 && i + 2 < utf8Str.size()) { + cp = ((c & 0x0F) << 12) | ((static_cast(utf8Str[i + 1]) & 0x3F) << 6) | (static_cast(utf8Str[i + 2]) & 0x3F); i += 3; + } else if ((c & 0xF8) == 0xF0 && i + 3 < utf8Str.size()) { + cp = ((c & 0x07) << 18) | ((static_cast(utf8Str[i + 1]) & 0x3F) << 12) | ((static_cast(utf8Str[i + 2]) & 0x3F) << 6) | (static_cast(utf8Str[i + 3]) & 0x3F); i += 4; + } else { i++; continue; } + if (cp <= 0xFFFF) { char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", cp); result += buf; } + else { cp -= 0x10000; char buf[16]; snprintf(buf, sizeof(buf), "\\u%04x\\u%04x", 0xD800 + (uint16_t)(cp >> 10), 0xDC00 + (uint16_t)(cp & 0x3FF)); result += buf; } + } + return result; } - std::string ANSFRHelper::UserRecordsToJsonString(const std::vector userRecords) { - boost::property_tree::ptree root; - boost::property_tree::ptree userRecordNodes; - for (int i=0; i < userRecords.size(); i++) { - boost::property_tree::ptree userRecordNode; - boost::property_tree::ptree faceIds; - UserRecord userRecord = userRecords[i]; - for (int j = 0; j < userRecord.FaceIds.size(); j++) { - boost::property_tree::ptree faceIdNode; - faceIdNode.put("", userRecord.FaceIds[j]); - faceIds.push_back(std::make_pair("", faceIdNode)); + + // Using nlohmann/json (consistent with FaceObjectsToJsonString) + std::string ANSFRHelper::UserRecordToJsonString(const UserRecord userRecord) { + try { + nlohmann::json faceIdArray = nlohmann::json::array(); + for (const auto& faceId : userRecord.FaceIds) { + faceIdArray.push_back(faceId); } - userRecordNode.put("user_id", userRecord.UserId); - userRecordNode.put("user_code", userRecord.UserCode); - userRecordNode.put("user_username", userRecord.UserName); - userRecordNode.add_child("face_ids", faceIds); - userRecordNodes.push_back(std::make_pair("", userRecordNode)); + nlohmann::json root = { + {"user_id", std::to_string(userRecord.UserId)}, + {"user_code", userRecord.UserCode}, + {"user_username", DoubleEscapeUnicode(userRecord.UserName)}, + {"face_ids", faceIdArray} + }; + return root.dump(); + } + catch (const std::exception&) { + return R"({})"; + } + } + + // Using nlohmann/json (consistent with FaceObjectsToJsonString) + std::string ANSFRHelper::UserRecordsToJsonString(const std::vector userRecords) { + try { + nlohmann::json root; + auto& records = root["user_records"] = nlohmann::json::array(); + + for (const auto& userRecord : userRecords) { + nlohmann::json faceIdArray = nlohmann::json::array(); + for (const auto& faceId : userRecord.FaceIds) { + faceIdArray.push_back(faceId); + } + records.push_back({ + {"user_id", std::to_string(userRecord.UserId)}, + {"user_code", userRecord.UserCode}, + {"user_username", DoubleEscapeUnicode(userRecord.UserName)}, + {"face_ids", faceIdArray} + }); + } + return root.dump(); + } + catch (const std::exception&) { + return R"({"user_records":[]})"; } - root.add_child("user_records", userRecordNodes); - std::ostringstream stream; - boost::property_tree::write_json(stream, root,false); - std::string userRecordResult = stream.str(); - return userRecordResult; } std::string ANSFRHelper::FaceRecordToJsonString(const FaceRecord faceRecord) { boost::property_tree::ptree root; diff --git a/tests/ANSFR-UnitTest/CMakeLists.txt b/tests/ANSFR-UnitTest/CMakeLists.txt index c553704..8e239d5 100644 --- a/tests/ANSFR-UnitTest/CMakeLists.txt +++ b/tests/ANSFR-UnitTest/CMakeLists.txt @@ -37,6 +37,7 @@ target_include_directories(ANSFR-UnitTest PRIVATE ${ANSLIBS_DIR}/faiss ${ANSLIBS_DIR}/TensorRT/include ${ANSLIBS_DIR}/fastdeploy_gpu/include + ${ANSLIBS_DIR}/nlohmann ) target_link_libraries(ANSFR-UnitTest