让UE4支持Json嵌套

栏目: 后端 · 前端 · 发布时间: 5年前

内容简介:Tips: 让UE4支持Json嵌套 (How to Support Nested at UE4)UE4项目开发笔记(三)开UE4的坑已经五月有余,这五个月里用各种姿势躺过不同的坑,有横着躺的;有竖着躺的;有睁着眼躺的…Orz…

Tips: 让UE4支持Json嵌套 (How to Support Nested at UE4)

UE4项目开发笔记(三)

开UE4的坑已经五月有余,这五个月里用各种姿势躺过不同的坑,有横着躺的;有竖着躺的;有睁着眼躺的…Orz…

这周躺了一个老坑,此坑在Unity也被蹂躏过一次.(别以为换了皮肤我就不认识你了 ( ╯□╰ ))

0x00. 基础知识

简单唠一句 , UE4在自己的反射机制之上实现的JSON 序列化反序列化 . 并非使用目前主流的任何JSON C++库.(libJson jsoncpp jsonFx rapidjson or other else..)

0x01. 问题再现

项目里几乎所有数据都使用了Json,用户生产的数据使用Json保存,网络请求数据使用Json往返. 由于小伙伴们设计的数据结构实在复杂 , 不免产生各种复杂的结构体 , 例如:

TMap < FString,FString > mapFF;
TMap < FString,TArray < FJsonStruct1 > > mapFTA;
TMap < FString,TMap < FString,FJsonStruct1 > > mapFTM;

以上三种数据变量 ,mapFF 是一个普通的字典变量 , 这个在UE4里是没问题可以正常, 在大多数UE4支持的UPROPERTY变量中 通过 FJsonObjectConverter 序列化和反序列化的.一般的处理方式是这样的:

// 定义F型数据结构
USTRUCT()
struct FJsonStruct1
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY()
        int nIndex;
    UPROPERTY()
        TMap < FString, FString > mapFF;
    FJsonStruct1()
    {
        mapFF.Empty();
    }
};
// JSON序列化处理
FJsonStruct1 f1;
f1.nIndex = 100;
f1.mapFF.Add("A1", "Value1");
f1.mapFF.Add("A2", "Value2");
FString strToJson;
if (FJsonObjectConverter::UStructToJsonObjectString(f1, strToJson, 0, 0))
{
    // strToJson == { "nIndex":100,"mapFF":{ "A1": "Value1", "A2": "Value2"  }}
}
// JSON反序列化处理
FJsonStruct1 f2;
if (FJsonObjectConverter::JsonObjectStringToUStruct(strToJson,&f2,0,0))
{
    // do something
}

一般情况下 通过以上代码片段 即处理绝大部分的JSON数据结构, 但是遇到嵌套型的数据结构 就要歇菜了.举个例子:

// 定义F型数据结构
USTRUCT()
struct FJsonStruct2
{
    GENERATED_USTRUCT_BODY()

    UPROPERTY()
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;
    FJsonStruct2()
    {
        mapFTA.Empty();
    }
};

以上结构定义 , 编译的时候UE4会爆出ERROR:

error : Nested containers are not supported.(嵌套的结构并不支持)

通过以上信息跟踪报错代码大概在 – UHT – HeaderParser.cpp , 通过阅读UHT源码 发现如果改动UHT让它支持嵌套结构 这也是一个解决方案.

接下来我们看看UE4的Json模块,代码位置在 Json , JsonUtilities , 通过阅读源码 , 我们发现UE4的Json 序列化 反序列化是依赖 UE4的底层机制实现的 , 也就是说 如果我们遇到嵌套型的数据结构 换一种方式进行 序列化/反序列化 也可以解决我们的问题.

所以现在问题就很清晰了 解决方案有两个:

  1. 修改UnrealBuildTool 让UE4的反射系统支持嵌套变量
  2. 修改UE4 Json模块 让Json模块支持嵌套变量的序列化反序列化

0x02. 解决思路分析

  • 修改UnrealBuildTool 让UE4的反射系统支持嵌套变量

这个是我首选的方案 , 在尝试了几天之后只能放弃 , 放弃理由:修改每一句UBT代码 就会导致UE4全体的重新编译,通过修改UBT让UE4支持嵌套变量 进而实现解析JSON嵌套数据 感觉有一点杀鸡牛刀 (其实是自我安慰..UBT功力不足改不动哇..泪目) , 所以我们使用第二种思路.

  • 修改UE4 Json模块 让Json模块支持嵌套变量的序列化反序列化

通过阅读UE4 Json源码 我们很容易可以发现几个地方:

// 反序列化的关键位置 JsonObjectConverter.cpp -> JsonAttributesToUStruct
bool FJsonObjectConverter::JsonAttributesToUStruct(
const TMap< FString, TSharedPtr
<fjsonvalue>
  >& JsonAttributes
, const UStruct* StructDefinition
, void* OutStruct
, int64 CheckFlags
, int64 SkipFlags)
{
    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        return true;
    }
    // ... 省略一吨代码
}
// 序列化关键地方 JsonObjectConverter.cpp -> UStructToJsonAttributes
bool FJsonObjectConverter::UStructToJsonAttributes(
const UStruct* StructDefinition
, const void* Struct
, TMap< FString, TSharedPtr
 <fjsonvalue>
   >& OutJsonAttributes
, int64 CheckFlags, int64 SkipFlags
, const CustomExportCallback* ExportCb)
{
    if (SkipFlags == 0)
    {
        // If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing
        SkipFlags |= CPF_Deprecated | CPF_Transient;
    }

    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper *)Struct;

        if (ProxyObject->JsonObject.IsValid())
        {
            OutJsonAttributes = ProxyObject->JsonObject->Values;
        }
        return true;
    }
    // ... 省略一吨代码
}

 </fjsonvalue>
</fjsonvalue>

以上两处代码 , 分别是执行 序列化 (Struct 2 String) 和 反序列化(String 2 Struct) 的关键位置 , 我们可以看到 在这个地方 JsonObject 已经被Parser出来 , 这个JsonObject 就是当前变量的JsonObject. 我们可以在这里加入我们的自定义解析.

0x03 . 解决方案(若你遇到此问题 以下内容请务必逐字阅读)

针对序列化和反序列化 嵌套型结构体 例如如下数据结构:

USTRUCT()
struct FJsonStruct2 
{
    GENERATED_USTRUCT_BODY()
    UPROPERTY()
        int nIndex;
    // UPROPERTY() --> 注意 我们不需要加 UPROPERTY
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;
}

第一步 在UE4 Json底层添加嵌套自定义结构体 – 添加到 JsonObjectWrapper.h

// Add this Struct in JsonObjectWrapper.h
USTRUCT(BlueprintType)
struct JSONUTILITIES_API FJsonObjectNested
{
    GENERATED_USTRUCT_BODY()
public:
    UPROPERTY(EditAnywhere, Category = "JSON")
        FString JsonString;
    TSharedPtr
<fjsonobject>
  JsonObject;

    explicit operator bool() const
    {
        return JsonObject.IsValid();
    }
    virtual ~FJsonObjectNested(){}
    virtual void JsonObjectToUStruct() {}
    virtual void UStructToJsonObject(TMap< FString, TSharedPtr
 <fjsonvalue>
   >& OutJsonAttributes) {}
};

 </fjsonvalue>
</fjsonobject>

第二步 在UE4 Json底层添加 嵌套数据结构序列化自定义操作 – 添加到 JsonObjectConverter.cpp

bool FJsonObjectConverter::UStructToJsonAttributes(
const UStruct* StructDefinition
, const void* Struct
, TMap< FString, TSharedPtr
<fjsonvalue>
  >& OutJsonAttributes
, int64 CheckFlags
, int64 SkipFlags
, const CustomExportCallback* ExportCb)
{
    if (SkipFlags == 0)
    {
        // If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing
        SkipFlags |= CPF_Deprecated | CPF_Transient;
    }

    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper *)Struct;

        if (ProxyObject->JsonObject.IsValid())
        {
            OutJsonAttributes = ProxyObject->JsonObject->Values;
        }
        return true;
    }
    //-------------------------------------------------------------------------
    // 自定义序列化操作开始
    // set convert the Neste property  on the output object by Rect 2018-10-10 14:57
    if (StructDefinition->GetSuperStruct() == FJsonObjectNested::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectNested* ProxyObject = (FJsonObjectNested *)Struct;
        ProxyObject->UStructToJsonObject(OutJsonAttributes);
    }
    // 自定义序列化操作结束
    //-------------------------------------------------------------------------
    // .. 省略一吨代码
}

</fjsonvalue>

第三步 在UE4 Json底层添加 嵌套数据结构反序列化自定义操作 – 添加到 JsonObjectConverter.cpp

bool FJsonObjectConverter::JsonAttributesToUStruct(
const TMap< FString, TSharedPtr
<fjsonvalue>
  >& JsonAttributes
, const UStruct* StructDefinition
, void* OutStruct, int64 CheckFlags, int64 SkipFlags)
{
    if (StructDefinition == FJsonObjectWrapper::StaticStruct())
    {
        // Just copy it into the object
        FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        return true;
    }

    //-------------------------------------------------------------------------
    // 自定义反序列化操作开始
    //  find a json value matching Neste property  by Rect 2018-10-10 14:57
    if (StructDefinition->GetSuperStruct() == FJsonObjectNested::StaticStruct())
    {
        FJsonObjectNested* ProxyObject = (FJsonObjectNested *)OutStruct;
        ProxyObject->JsonObject = MakeShareable(new FJsonObject());
        ProxyObject->JsonObject->Values = JsonAttributes;
        ProxyObject->JsonObjectToUStruct();
    }
    // 自定义反序列化操作结束
    //-------------------------------------------------------------------------
    // .. 省略一吨代码
}

</fjsonvalue>

第四步 修改 FJsonStruct2 继承 FJsonObjectNested 并重写 JsonObjectToUStruct,UStructToJsonObject函数

USTRUCT()
struct FJsonStruct2 : public FJsonObjectNested // 继承 FJsonObjectNested
{
    GENERATED_USTRUCT_BODY()

    // UPROPERTY()
        TMap < FString, TArray < FJsonStruct1 > > mapFTA;

    FJsonStruct2()
    {
        mapFTA.Empty();
    }
    // 重写 JsonObjectToUStruct
    virtual void JsonObjectToUStruct() override
    {
        const FString key = TEXT("mapFTA");
        if (false == JsonObject.IsValid() || false == JsonObject->HasTypedField
<ejson::object>
 (key))
        {
            return;
        }

        TSharedPtr
 <fjsonobject>
   JOmapFTA = JsonObject->GetObjectField(key);

        for (auto e = JOmapFTA->Values.CreateIterator(); e; ++e)
        {
            // key
            FString keyMap = e.Key();
            // Array
            TArray
  <tsharedptr>
   <fjsonvalue>
    > JsonParsed = JOmapFTA->GetArrayField(keyMap);
            TArray
    <fjsonstruct1>
      argvArr;
            for (auto argv : JsonParsed)
            {
                FJsonStruct1 fff;
                if (FJsonObjectConverter::JsonObjectToUStruct(argv->AsObject().ToSharedRef(), &fff))
                {
                    argvArr.Add(fff);
                }
            }
            mapFTA.Add(keyMap, argvArr);
        }
    }
    // 重写 UStructToJsonObject
    virtual void UStructToJsonObject(TMap< FString, TSharedPtr
     <fjsonvalue>
       >& OutJsonAttributes) override
    {
        const FString VariableName = TEXT("mapFTA");
        if (true == OutJsonAttributes.Contains(VariableName) || 0 == mapFTA.Num())
        {
            return;
        }

        TSharedPtr
      <fjsonobject>
        JOmapFTA = MakeShareable(new FJsonObject);

        for (auto e = mapFTA.CreateIterator(); e; ++e)
        {
            // key
            FString keyMap = e.Key();
            TArray
       <fjsonstruct1>
         valueMap = e.Value();
            if (0 == valueMap.Num())
            {
                continue;
            }
            // Array
            TSharedPtr
        <fjsonobject>
          ObjectArray = MakeShareable(new FJsonObject);
            TArray
         <tsharedptr>
          <fjsonvalue>
            > JsonValueArray;
            for (auto v : valueMap)
            {
                FString toJson;
                TSharedRef
           <fjsonobject>
             JOmapFTA = MakeShareable(new FJsonObject());
                if (FJsonObjectConverter::UStructToJsonObject(FJsonStruct1::StaticStruct(), &v, JOmapFTA, 0, 0))
                {
                    TSharedPtr
            <fjsonvalue>
              JVFTileData = MakeShareable(new FJsonValueObject(JOmapFTA));
                    JsonValueArray.Add(JVFTileData);
                }
            }

            JOmapFTA->SetArrayField(keyMap, JsonValueArray);
        }

        TSharedPtr
            </fjsonvalue>
            <fjsonvalue>
              JVmapFTA = MakeShareable(new FJsonValueObject(JOmapFTA));
        OutJsonAttributes.Add(VariableName, JVmapFTA);
    }
};

            </fjsonvalue>
           </fjsonobject>
          </fjsonvalue>
         </tsharedptr>
        </fjsonobject>
       </fjsonstruct1>
      </fjsonobject>
     </fjsonvalue>
    </fjsonstruct1>
   </fjsonvalue>
  </tsharedptr>
 </fjsonobject>
</ejson::object>

如此便可实现 UE4中 对于JSON嵌套型数据结构的 序列化和反序列化

-EOF-


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Ajax Design Patterns

Ajax Design Patterns

Michael Mahemoff / O'Reilly Media / 2006-06-29 / USD 44.99

Ajax, or Asynchronous JavaScript and XML, exploded onto the scene in the spring of 2005 and remains the hottest story among web developers. With its rich combination of technologies, Ajax provides a s......一起来看看 《Ajax Design Patterns》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具