pb2json.hpp 文档:Protobuf 与 JSON 通用转换工具类

pb2json.hpp 文档:Protobuf 与 JSON 通用转换工具类

一、文件概述

pb2json.hpp 是一个基于 C++ 的通用工具类头文件,用于实现 Protobuf 消息与 JSON 格式(通过 jsoncpp 库)的双向转换。该类利用 Protobuf 的反射(Reflection)机制,支持任意 Protobuf 消息类型的转换,无需为特定消息编写专用代码。

二、核心功能

  • 支持 Protobuf 消息与 jsoncpp 的 Json::Value 互转
  • 支持 Protobuf 消息与 JSON 字符串互转
  • 兼容 Protobuf 常见数据类型(基础类型、枚举、嵌套消息、重复字段等)
  • 提供枚举类型转换选项(转为字符串或整数)

三、代码实现(内嵌完整代码)

cpp 复制代码
#ifndef MYLIB_JSON_2_PB
# define MYLIB_JSON_2_PB

#include "jsoncpp/json/json.h"
#include "stdint.h"

#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>

class NpJson2Pb {
 
public:
    typedef ::google::protobuf::Message         ProtobufMsg;
    typedef ::google::protobuf::Reflection      ProtobufReflection;
    typedef ::google::protobuf::FieldDescriptor ProtobufFieldDescriptor;
    typedef ::google::protobuf::Descriptor      ProtobufDescriptor;
 
public:

    /**
     * Protobuf 消息转 JSON 字符串
     * @param src 源 Protobuf 消息
     * @param dst 输出的 JSON 字符串
     * @param enum2str 枚举是否转为字符串(默认 true)
     */
    static void PbMsg2JsonStr(const ProtobufMsg& src, std::string& dst, bool enum2str = true) 
    {
        Json::Value value;
        PbMsg2Json(src, value, enum2str);
        Json::FastWriter writer;
        dst = writer.write(value);
    }
 
    /**
     * JSON 字符串转 Protobuf 消息
     * @param src 源 JSON 字符串
     * @param dst 输出的 Protobuf 消息
     * @param str2enum 是否从字符串解析枚举(默认 true)
     * @return 转换成功返回 true,失败返回 false
     */
    static bool JsonStr2PbMsg(const std::string& src, ProtobufMsg& dst, bool str2enum = true) 
    {
        Json::Value value;
        Json::Reader reader(Json::Features::strictMode());
        if (!reader.parse(src, value))
        {
            printf("[JsonStr2PbMsg]parse json string is fail,str=%s", src.c_str());
            return false;
        }
        if(true != Json2PbMsg(value, dst, str2enum))
        {
            printf("[JsonStr2PbMsg]pb convert error");
            return false;
        }
        return true;
    }

    /**
     * Json::Value 转 Protobuf 消息
     * @param src 源 Json::Value 对象
     * @param dst 输出的 Protobuf 消息
     * @param str2enum 是否从字符串解析枚举(默认 true)
     * @return 转换成功返回 true,失败返回 false
     */
    static bool Json2PbMsg(const Json::Value& src, ProtobufMsg& dst, bool str2enum = true)
    {
        const ProtobufDescriptor* descriptor = dst.GetDescriptor();
        const ProtobufReflection* reflection = dst.GetReflection();
        if (NULL == descriptor || NULL == reflection) return false;
 
        int32_t count = descriptor->field_count();
        for (int32_t i = 0; i < count; ++i) 
        {
            const ProtobufFieldDescriptor* field = descriptor->field(i);
            if (NULL == field) continue;
 
            if(!src.isMember(field->name()))
            {
                continue;
            }
            const Json::Value& value = src[field->name()];
 
            if (field->is_repeated()) 
            {
                if (!value.isArray()) 
                {
                    printf("[Json2PbMsg] Json is not ary!");
                    return false;
                } 
                else 
                {
                    Json2RepeatedMessage(value, dst, field, reflection, str2enum);
                    continue;
                }
            }
            switch (field->type()) 
            {
                case ProtobufFieldDescriptor::TYPE_BOOL: 
                {
                    if (value.isBool())
                    {
                        reflection->SetBool(&dst, field, value.asBool());
                    }
                    else if (value.isInt())
                    {
                        reflection->SetBool(&dst, field, value.isInt());
                    }
                    else if (value.isString())
                    {
                        if (value.asString() == "true")
                            reflection->SetBool(&dst, field, true);
                        else if (value.asString() == "false")
                            reflection->SetBool(&dst, field, false);
                    }
                    break;
                }

                case ProtobufFieldDescriptor::TYPE_ENUM: 
                {
                    const ::google::protobuf::EnumDescriptor *pedesc = field->enum_type();
                    const ::google::protobuf::EnumValueDescriptor* pevdesc = NULL;
                    if (str2enum) 
                    {
                        pevdesc = pedesc->FindValueByName(value.asString());
                    } 
                    else 
                    {
                        pevdesc = pedesc->FindValueByNumber(value.asInt());
                    }
                    if (NULL != pevdesc) 
                    {
                        reflection->SetEnum(&dst, field, pevdesc);
                    }
                    break;
                }
 
                case ProtobufFieldDescriptor::TYPE_INT32:
                case ProtobufFieldDescriptor::TYPE_SINT32:
                case ProtobufFieldDescriptor::TYPE_SFIXED32: 
                {
                    if (value.isInt())
                    {
                        reflection->SetInt32(&dst, field, value.asInt());
                    }
                    break;
                }
 
                case ProtobufFieldDescriptor::TYPE_UINT32:
                case ProtobufFieldDescriptor::TYPE_FIXED32: 
                {
                    if (value.isUInt() || value.isInt())
                    {
                        reflection->SetUInt32(&dst, field, value.asUInt());
                    }
                    break;
                }  
 
                case ProtobufFieldDescriptor::TYPE_INT64:
                case ProtobufFieldDescriptor::TYPE_SINT64:
                case ProtobufFieldDescriptor::TYPE_SFIXED64: 
                {
                    if (value.isInt())
                    {
                        reflection->SetInt64(&dst, field, npatoll(value.asString().c_str()));
                    }
                    break;
                } 
                case ProtobufFieldDescriptor::TYPE_UINT64:
                case ProtobufFieldDescriptor::TYPE_FIXED64: 
                {
                    if (value.isUInt() || value.isInt())
                    {
                        reflection->SetUInt64(&dst, field, npatoll(value.asString().c_str()));
                    }
                    break;
                } 
 
                case ProtobufFieldDescriptor::TYPE_FLOAT: 
                {
                    if (value.isDouble())
                    {
                        reflection->SetFloat(&dst, field, atof(value.asString().c_str()));
                    }
                    break;
                }
 
                case ProtobufFieldDescriptor::TYPE_DOUBLE: 
                {
                    if (value.isDouble())
                    {
                        reflection->SetDouble(&dst, field, value.asDouble());
                    }
                    break;
                } 
 
                case ProtobufFieldDescriptor::TYPE_STRING:
                case ProtobufFieldDescriptor::TYPE_BYTES: 
                {
                    if (value.isString())
                    {
                        reflection->SetString(&dst, field, value.asString());
                    }
                    break;
                }
 
                case ProtobufFieldDescriptor::TYPE_MESSAGE: 
                {
                    if (value.isObject())
                    {
                        Json2PbMsg(value, *reflection->MutableMessage(&dst, field), str2enum);
                    }
                    break;
                } 
                default:
                {
                    printf("[Json2PbMsg] no invalid Type!");
                    break;
                }
            }
        }
        return true;
    }
 
    /**
     * Protobuf 消息转 Json::Value
     * @param src 源 Protobuf 消息
     * @param dst 输出的 Json::Value 对象
     * @param enum2str 枚举是否转为字符串(默认 true)
     */
    static void PbMsg2Json(const ProtobufMsg& src, Json::Value& dst, bool enum2str = true) 
    {
        const ProtobufDescriptor* descriptor = src.GetDescriptor();
        const ProtobufReflection* reflection = src.GetReflection();
        if (NULL == descriptor || NULL == descriptor) return;
 
        int32_t count = descriptor->field_count();
 
        for (int32_t i = 0; i < count; ++i) 
        {
            const ProtobufFieldDescriptor* field = descriptor->field(i);
            
            if (field->is_repeated()) 
            {
                if (reflection->FieldSize(src, field) > 0)
                    RepeatedMessage2Json(src, field, reflection, dst[field->name()], enum2str);
                continue;
            }
  
            if (!reflection->HasField(src, field)) 
            {
                continue;
            }
 
            switch (field->type()) 
            {
                case ProtobufFieldDescriptor::TYPE_MESSAGE: 
                {
                    ProtobufMsg* ptMsg = (ProtobufMsg*)&src;
                    const ProtobufMsg* tmp_message = reflection->MutableMessage(ptMsg, field);
                    if (0 != tmp_message->ByteSize())
                    {
                        PbMsg2Json(*tmp_message, dst[field->name()], enum2str);
                    }
                    break;
                } 

                case ProtobufFieldDescriptor::TYPE_BOOL:
                    dst[field->name()] = reflection->GetBool(src, field) ? true : false;
                    break;

                case ProtobufFieldDescriptor::TYPE_ENUM: 
                {
                    const ::google::protobuf::EnumValueDescriptor* enum_value_desc = reflection->GetEnum(src, field);
                    if (enum2str) {
                        dst[field->name()] = enum_value_desc->name();
                    } else {
                        dst[field->name()] = enum_value_desc->number();
                    }
                    break;
                } 

                case ProtobufFieldDescriptor::TYPE_INT32:
                case ProtobufFieldDescriptor::TYPE_SINT32:
                case ProtobufFieldDescriptor::TYPE_SFIXED32:
                    dst[field->name()] = Json::Int(reflection->GetInt32(src, field));
                    break;

                case ProtobufFieldDescriptor::TYPE_UINT32:
                case ProtobufFieldDescriptor::TYPE_FIXED32:
                    dst[field->name()] = Json::UInt(reflection->GetUInt32(src, field));
                    break;

                case ProtobufFieldDescriptor::TYPE_INT64:
                case ProtobufFieldDescriptor::TYPE_SINT64:
                case ProtobufFieldDescriptor::TYPE_SFIXED64:
                {
                    static char int64str[32];
                    memset(int64str, 0, sizeof(int64str));
                    sprintf(int64str, "%lld", (s64)reflection->GetRepeatedInt64(src, field, i));
                    dst[field->name()] = int64str;
                }
                // jsoncpp 不支持 INT64,暂时使用 string
                break;

                case ProtobufFieldDescriptor::TYPE_UINT64:
                case ProtobufFieldDescriptor::TYPE_FIXED64:
                {
                    static char uint64str[32];
                    memset(uint64str, 0, sizeof(uint64str));
                    sprintf(uint64str, "%llu", (u64)reflection->GetRepeatedInt64(src, field, i));
                    dst[field->name()] = uint64str;
                }
                // jsoncpp 不支持 UINT64,暂时使用 string
                break;

                case ProtobufFieldDescriptor::TYPE_FLOAT:
                    dst[field->name()] = reflection->GetFloat(src, field);
                    break;

                case ProtobufFieldDescriptor::TYPE_DOUBLE: 
                {
                    dst[field->name()] = reflection->GetDouble(src, field);
                    break;
                }

                case ProtobufFieldDescriptor::TYPE_STRING:
                case ProtobufFieldDescriptor::TYPE_BYTES:
                    dst[field->name()] = reflection->GetString(src, field);
                    break;

                default:
                    printf("[PbMsg2Json] no invalid Type!");
                    break;
            }
        }
    }

private:
    /**
     * JSON 数组转 Protobuf 重复字段
     * @param json 源 JSON 数组
     * @param message 目标 Protobuf 消息
     * @param field 字段描述符
     * @param reflection 反射接口
     * @param str2enum 是否从字符串解析枚举
     * @return 转换成功返回 true
     */
    static bool Json2RepeatedMessage(const Json::Value& json, ProtobufMsg& message, 
                                    const ProtobufFieldDescriptor* field,
                                    const ProtobufReflection* reflection,
                                    bool str2enum = true) 
    {
        int32_t count = json.size();
        Json::Value jsonforinex;
        for (int32_t j = 0; j < count; ++j) 
        {
            if (json[j].type() == Json::objectValue && field->type() != ProtobufFieldDescriptor::TYPE_MESSAGE)
            {
                jsonforinex = json[j][field->name()];
            }
            else
            {
                jsonforinex = json[j];
            }
            switch (field->type()) 
            {
                case ProtobufFieldDescriptor::TYPE_BOOL: 
                {
                    if (jsonforinex.isBool())
                    {
                        reflection->AddBool(&message, field, jsonforinex.asBool());
                    }
                    else if (jsonforinex.isInt())
                    {
                        reflection->AddBool(&message, field, jsonforinex.asInt());
                    }
                    else if (jsonforinex.isString()) 
                    {
                        if (jsonforinex.asString() == "true")
                        {
                            reflection->AddBool(&message, field, true);
                        }
                        else if (jsonforinex.asString() == "false")
                        {
                            reflection->AddBool(&message, field, false);
                        }
                    }
                    break;
                }
                case ProtobufFieldDescriptor::TYPE_ENUM: 
                {
                    const ::google::protobuf::EnumDescriptor *pedesc = field->enum_type();
                    const ::google::protobuf::EnumValueDescriptor* pevdesc = NULL;
                    if (str2enum) 
                    {
                        pevdesc = pedesc->FindValueByName(jsonforinex.asString());
                    } 
                    else 
                    {
                        pevdesc = pedesc->FindValueByNumber(jsonforinex.asInt());
                    }
                    if (NULL != pevdesc) 
                    {
                        reflection->AddEnum(&message, field, pevdesc);
                    }
                    break;
                }
                case ProtobufFieldDescriptor::TYPE_INT32:
                case ProtobufFieldDescriptor::TYPE_SINT32:
                case ProtobufFieldDescriptor::TYPE_SFIXED32: 
                {
                    if (jsonforinex.isInt())
                    {
                        reflection->AddInt32(&message, field, jsonforinex.asInt());
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_UINT32:
                case ProtobufFieldDescriptor::TYPE_FIXED32: 
                {
                    if (jsonforinex.isUInt() || jsonforinex.isInt())
                    {
                        reflection->AddUInt32(&message, field, jsonforinex.asUInt());
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_INT64:
                case ProtobufFieldDescriptor::TYPE_SINT64:
                case ProtobufFieldDescriptor::TYPE_SFIXED64: 
                {
                    if (jsonforinex.isInt())
                    {
                        reflection->AddInt64(&message, field, npatoll(jsonforinex.asString().c_str()));
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_UINT64:
                case ProtobufFieldDescriptor::TYPE_FIXED64: 
                {
                    if (jsonforinex.isUInt() || jsonforinex.isInt())
                    {
                        reflection->AddUInt64(&message, field, npatoll(jsonforinex.asString().c_str()));
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_FLOAT: 
                {
                    if (jsonforinex.isDouble())
                    {
                        reflection->AddFloat(&message, field, atof(jsonforinex.asString().c_str()));
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_DOUBLE: 
                {
                    if (jsonforinex.isDouble())
                    {
                        reflection->AddDouble(&message, field, jsonforinex.asDouble());
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_MESSAGE: 
                {
                    if (jsonforinex.isObject())
                    {
                        Json2PbMsg(jsonforinex, *reflection->AddMessage(&message, field), str2enum);
                    }
                } break;
 
                case ProtobufFieldDescriptor::TYPE_STRING:
                case ProtobufFieldDescriptor::TYPE_BYTES: 
                {
                    if (jsonforinex.isString())
                    {
                        reflection->AddString(&message, field, jsonforinex.asString());
                    }
                } break;
 
                default:
                {
                    printf("[Json2RepeatedMessage] no invalid Type!");
                    break;
                }
            }
        }
        return true;
    }
 
    /**
     * Protobuf 重复字段转 JSON 数组
     * @param message 源 Protobuf 消息
     * @param field 字段描述符
     * @param reflection 反射接口
     * @param json 输出的 JSON 数组
     * @param enum2str 枚举是否转为字符串
     */
    static void RepeatedMessage2Json(const ProtobufMsg& message, 
                                            const ProtobufFieldDescriptor* field,
                                            const ProtobufReflection* reflection, 
                                            Json::Value& json, bool enum2str) 
    {
        if (NULL == field || NULL == reflection) 
        {
            PbMsg2Json(message, json, enum2str);
        }
 
        for (int32_t i = 0; i < reflection->FieldSize(message, field); ++i) 
        {
            Json::Value tmp_json;
            switch (field->type()) 
            {
                case ProtobufFieldDescriptor::TYPE_BOOL:
                    tmp_json[field->name()] = reflection->GetRepeatedBool(message, field, i) ? true : false;
                    break;
                case ProtobufFieldDescriptor::TYPE_ENUM: 
                {
                    const ::google::protobuf::EnumValueDescriptor* enum_value_desc = reflection->GetRepeatedEnum(message, field, i);
                    if (enum2str) 
                    {
                        tmp_json = enum_value_desc->name();
                    } 
                    else 
                    {
                        tmp_json = enum_value_desc->number();
                    }
                    break;
                } 
                case ProtobufFieldDescriptor::TYPE_INT32:
                case ProtobufFieldDescriptor::TYPE_SINT32:
                case ProtobufFieldDescriptor::TYPE_SFIXED32:
                    tmp_json[field->name()] = reflection->GetRepeatedInt32(message, field, i);
                    break;
                case ProtobufFieldDescriptor::TYPE_UINT32:
                case ProtobufFieldDescriptor::TYPE_FIXED32:
                    tmp_json[field->name()] = reflection->GetRepeatedUInt32(message, field, i);
                    break;
                case ProtobufFieldDescriptor::TYPE_INT64:
                case ProtobufFieldDescriptor::TYPE_SINT64:
                case ProtobufFieldDescriptor::TYPE_SFIXED64:
                {
                    static char int64str[32];
                    memset(int64str, 0, sizeof(int64str));
                    sprintf(int64str, "%lld", (s64)reflection->GetRepeatedInt64(message, field, i));
                    tmp_json[field->name()] = int64str;
                }
                break;
                case ProtobufFieldDescriptor::TYPE_UINT64:
                case ProtobufFieldDescriptor::TYPE_FIXED64:
                {
                    static char uint64str[32];
                    memset(uint64str, 0, sizeof(uint64str));
                    sprintf(uint64str, "%llu", (u64)reflection->GetRepeatedInt64(message, field, i));
                    tmp_json[field->name()] = uint64str;
                }
                break;
                case ProtobufFieldDescriptor::TYPE_FLOAT:
                    tmp_json[field->name()] = reflection->GetRepeatedFloat(message, field, i);
                    break;
                case ProtobufFieldDescriptor::TYPE_DOUBLE: 
                {
                    tmp_json[field->name()] = reflection->GetRepeatedDouble(message, field, i);
                    break;
                }
                case ProtobufFieldDescriptor::TYPE_STRING:
                case ProtobufFieldDescriptor::TYPE_BYTES:
                    tmp_json[field->name()] = reflection->GetRepeatedString(message, field, i);
                    break;

                case ProtobufFieldDescriptor::TYPE_MESSAGE: 
                {
                    const ProtobufMsg& tmp_message = reflection->GetRepeatedMessage(message, field, i);
                    if (0 != tmp_message.ByteSize()) 
                    {
                        PbMsg2Json(tmp_message, tmp_json, enum2str);
                    }
                    break;
                }
                default:
                {
                    printf("[RepeatedMessage2Json] no invalid Type!");
                }
                break;
            }
            json.append(tmp_json);
        }
    }

    /**
     * 字符串转长整型(处理 int64/uint64 转换)
     * @param p 输入字符串
     * @return 转换后的长整型值
     */
    static long long npatoll(const char *p)
    {
        long long n;
        int c, neg = 0;
        unsigned char   *up = (unsigned char *)p;

        if (!isdigit(c = *up)) {
            while (isspace(c))
                c = *++up;
            switch (c) {
            case '-':
                neg++;
                /* FALLTHROUGH */
            case '+':
                c = *++up;
            }
            if (!isdigit(c))
                return (0);
        }

        for (n = '0' - c; isdigit(c = *++up); ) {
            n *= 10; /* 分两步避免溢出 */
            n += '0' - c; /* 累加负数避免最大值处理问题 */
        }

        return (neg ? n : -n);
    }
 
};

#endif

四、类与方法说明

1. 核心类:NpJson2Pb

  • 静态类,所有方法均为静态,无需实例化即可使用
  • 依赖:jsoncpp 库(处理 JSON)和 Protobuf 库(提供反射接口)
  • 类型别名:
    • ProtobufMsggoogle::protobuf::Message(Protobuf 消息基类)
    • ProtobufReflectiongoogle::protobuf::Reflection(反射接口)
    • ProtobufFieldDescriptorgoogle::protobuf::FieldDescriptor(字段描述符)
    • ProtobufDescriptorgoogle::protobuf::Descriptor(消息描述符)

2. 主要转换方法

方法名 功能 参数说明
PbMsg2JsonStr Protobuf 消息 → JSON 字符串 src:源 Protobuf 消息 dst:输出 JSON 字符串 enum2str:枚举是否转为字符串(默认 true)
JsonStr2PbMsg JSON 字符串 → Protobuf 消息 src:源 JSON 字符串 dst:输出 Protobuf 消息 str2enum:是否从字符串解析枚举(默认 true) 返回值:成功返回 true
PbMsg2Json Protobuf 消息 → Json::Value src:源 Protobuf 消息 dst:输出 Json::Value 对象 enum2str:枚举是否转为字符串(默认 true)
Json2PbMsg Json::Value → Protobuf 消息 src:源 Json::Value 对象 dst:输出 Protobuf 消息 str2enum:是否从字符串解析枚举(默认 true) 返回值:成功返回 true

3. 辅助方法(私有)

  • Json2RepeatedMessage:处理 JSON 数组到 Protobuf 重复字段(repeated)的转换
  • RepeatedMessage2Json:处理 Protobuf 重复字段到 JSON 数组的转换
  • npatoll:自定义字符串转长整型函数,解决 64 位整数转换精度问题

五、支持的数据类型

  • 基础类型:bool、int32、uint32、int64、uint64、float、double
  • 字符串类型:string、bytes
  • 复合类型:枚举(enum)、嵌套消息(message)、重复字段(repeated,数组)

六、实现细节

  1. 反射机制 :通过 Protobuf 的 Descriptor(描述符)和 Reflection(反射)接口,动态获取消息结构和字段值,实现通用转换(无需为特定消息编写代码)。

  2. 64位整数处理:由于 jsoncpp 对 int64/uint64 支持有限,转换时以字符串形式存储,避免精度丢失。

  3. 枚举类型转换

    • 可选转为字符串(枚举名)或整数(枚举值)
    • 转换选项通过 enum2str(Pb→Json)和 str2enum(Json→Pb)参数控制
  4. 嵌套消息 :通过递归调用 PbMsg2JsonJson2PbMsg 处理嵌套结构。

  5. 错误处理 :转换失败时通过 printf 输出错误信息(可根据需求改为异常抛出)。

七、使用示例

cpp 复制代码
// 假设已定义 User 类型的 Protobuf 消息
#include "user.pb.h"
#include "pb2json.hpp"

// Protobuf 转 JSON 字符串
User user;
user.set_id(1001);
user.set_name("Alice");
std::string json_str;
NpJson2Pb::PbMsg2JsonStr(user, json_str);  // 转换为 JSON 字符串

// JSON 字符串转 Protobuf
User user2;
bool ret = NpJson2Pb::JsonStr2PbMsg(json_str, user2);  // 转换回 Protobuf
if (ret) {
    // 转换成功,使用 user2
}

八、注意事项

  1. 依赖库需正确链接:编译时需链接 protobufjsoncpp 库。
  2. 64位整数以字符串存储:JSON 中 int64/uint64 类型会转为字符串,解析时需注意。
  3. 字段名匹配:JSON 字段名需与 Protobuf 字段名完全一致(区分大小写)。
  4. 枚举兼容性:若 JSON 中枚举字符串与 Protobuf 枚举名不匹配,转换会失败。
  5. 错误处理:当前通过 printf 输出错误,生产环境可改为日志或异常机制。

该工具类适用于需要在 Protobuf 与 JSON 之间进行灵活转换的场景(如 API 交互、配置解析等),支持任意 Protobuf 消息类型,通用性强。

相关推荐
万粉变现经纪人1 天前
何解决PyCharm中pip install安装Python报错ModuleNotFoundError: No module named ‘json’问题
python·pycharm·json·beautifulsoup·scikit-learn·matplotlib·pip
晨欣1 天前
orjson 与 json:实战对比与选型指南(含示例)(GPT-5 回答)
gpt·json
Pi_Qiu_2 天前
Python初学者笔记第二十二期 -- (JSON数据解析)
笔记·python·json
mon_star°2 天前
有趣的 npm 库 · json-server
前端·npm·json
ID_180079054732 天前
淘宝拍立淘按图搜索API接口功能详细说明
大数据·python·json·图搜索算法
cypking2 天前
vue excel转json功能 xlsx
vue.js·json·excel
我又来搬代码了3 天前
【Android】【bug】Json解析错误Expected BEGIN_OBJECT but was STRING...
android·json·bug
山里幽默的程序员4 天前
如何使用Postman无障碍导入JSON文件?详细步骤&工具对照
测试工具·json·postman
米饭的白色5 天前
vscode/trae 的 settings.json 中配置 latex 的一些记录
vscode·json·latex