DataConfig JSON Asset Book

Thumbnail_284_284

DataConfig JSON Asset (DcJsonAsset in short) is a UE plugin that does one particular thing:

Import JSON files into Unreal Engine as DataAsset

Notably features:

  • Relaxed JSON parser that supports comments and trailing comma.
  • Precise diagnostics on parse error.
  • Batch import and reimport.

It's built on top of DataConfig, a serialization framework for Unreal Engine.

Getting Started

Tutorial

This page shows a quick demo of how to use DcJsonAsset.

  1. First ensure that the plugin is properly integrated in your project. Open Settings -> Plugins and search for DataConfig JSON Asset:

    DataConfig JsonAsset Plugin

  2. We have bundled a simple Blueprint class DcTestBPDataAsset:

    DcTestBPDataAsset

    It's almost equivalent to a C++ UCLASS like this:

    UCLASS()
    class UDcTestBPDataAsset : public UDcPrimaryImportedDataAsset {
    GENERATED_BODY()
    public:
        UPROPERTY(EditAnywhere) FString StrField;
        UPROPERTY(EditAnywhere) int IntField;
        UPROPERTY(EditAnywhere) FName NameField;
    };
    
  3. Prepare a JSON file on disk with the content below:

    {
        "$type" : "/DcJsonAsset/DcFixture/DcTestBPDataAsset",
        "StrField" : "Hello DataConfig Json Asset",
        "IntField" : 43,
    }
    
  4. Then drop the file from the explorer into "Content Browser" and it's done.

    ImportExampleJson

  5. Say we made a mistake when editing the JSON file:

    {
        "$type" : "/DcJsonAsset/DcFixture/DcTestBPDataAsset",
        "StrField" : "Hello DataConfig Json Asset",
        "IntField" : 43,
        "NoField" : "Does not exist", // <- field doesn't exist in class
    }
    

    Right click on the Example asset and select reimport. DcJsonAsset would report this error in the "Message Log" window:

    MessageLogDiagnostics

Schema

This page documents the JSON schema that DataConfig JSON Asset supports.

Root $type

The first thing when importing a JSON asset is to determine which class to deserialize into. It's specified by the $type field in the JSON source:

// DcJsonAsset/Tests/DcJsonFixture_Simple.json
{
    "$type" : "DcJsonAssetTestPrimaryDataAsset1",
    "AlphaName" : "Foo",
    "AlphaBool" : true, 
    "AlphaStr" : "Bar"
}

We call all object keys with $ prefix meta fields. $type is one that's used a lot. Under the hood this $type field is handled separately from other fields:

  • $type must be the first field in the object. In fact all meta fields are order significant and needs to start as the beginning keys of a object.
  • $type values must refers to a UDcImportedDataAsset or UDcPrimaryImportedDataAsset children:
    • If it's a native C++ class, refer to it by the class name without the U prefix. For example: DcJsonAssetTestPrimaryDataAsset1
    • If it's a Blueprint Class, refer to it by the Blueprint asset path. For example: /DcJsonAsset/DcFixture/DcTestBPDataAsset

Scalar Types

Most UE primitive types has pretty straight forward mappings to JSON data types:

UE TypeJSON TypeExample
boolBooleantrue, false
String, Name, TextString"Foo"
int, uint, float, doubleNumber123, 234.5

We additionally support a few scalar like data types:

Enums

Enum can be deserialized from a string. Enum flags needs to be deserialized from a list of string:

// DcJsonAssetTests/Private/DcTestImports2.h
UCLASS()
class UDcJsonAssetTestDataAsset1_Enum : public UDcImportedDataAsset {
	// ...
	UPROPERTY(EditAnywhere) EDcJsonAssetTestEnum_Simple EnumField;
	UPROPERTY(EditAnywhere) EDcJsonAssetTestEnum_Flag EnumFlagField;
};

// DcJsonAsset/Tests/DcJsonFixture_Enum.json
{
    "$type" : "DcJsonAssetTestDataAsset1_Enum",
    "EnumField" : "Baz",
    "EnumFlagField" : ["Alpha", "Gamma"], 
}

GameplayTags

Gameplay Tag structs FGameplayTag and FGameplayTagContainer are both supported:

// DcJsonAssetTests/Private/DcTestImports2.h 
UCLASS()
class UDcJsonAssetTestDataAsset2_GameplayTag : public UDcImportedDataAsset {
	// ...
	UPROPERTY(EditAnywhere) FGameplayTag GameplayTagField1;
	UPROPERTY(EditAnywhere) FGameplayTag GameplayTagField2;
	UPROPERTY(EditAnywhere) FGameplayTagContainer GameplayTagContainer;
};

// DcJsonAsset/Tests/DcJsonFixture_GameplayTag.json
{
    "$type" : "DcJsonAssetTestDataAsset2_GameplayTag",
    "GameplayTagField1" : "DcJsonAsset.Foo.Bar.Baz",
    "GameplayTagField2" : null,
    "GameplayTagContainer" : [
        "DcJsonAsset.Foo.Bar",
        "DcJsonAsset.Tar.Taz"
    ]
}

Containers and Aggregates

UE containers mapping to JSON is also pretty unsurprising:

UE TypeJSON TypeExample
Array, SetArray["foo", "bar"]
Map, Struct, Class RootObject{"foo": "bar"}

Object References

UE provides a few options when referencing UObject instances:

TypeExample
Direct object referenceUObject*, AActor*
Soft object referenceTSoftObjectPtr<UObject>, TSoftClassPtr<UClass>
Lazy object referenceTLazyObjectPtr<UObject>
Weak Object ReferenceTWeakObjectPtr<UObject>

All of these are supported for completeness sake. The recommendation is:

  • Use direct or soft object reference for common object references. It's the usual way to referencing objects.
  • Use soft object reference when pointing to UDcImportedDataAsset/UDcPrimaryImportedDataAsset derived classes. It's specially handled that it won't load the object to deserialize. This means we can batch import a set of JSON assets that references each other, without the need to resolve the mutual dependencies among them. The downside is that we don't check that the object actually exist at import time.

We allow a few options when referencing an object. Below is a example of object references:

// DcJsonAssetTests/Private/DcTestImports2.h
UCLASS()
class UDcJsonAssetTestDataAsset3_References : public UDcImportedDataAsset {
	// ...	
	UPROPERTY(EditAnywhere) UObject* DirectRef1;
	UPROPERTY(EditAnywhere) UObject* DirectRef2;
	UPROPERTY(EditAnywhere) UObject* DirectRef3;
	UPROPERTY(EditAnywhere) TSoftObjectPtr<UDcPrimaryImportedDataAsset> DcSoftImported1;
	UPROPERTY(EditAnywhere) TSoftObjectPtr<UDcPrimaryImportedDataAsset> DcSoftImported2;
};

// DcJsonAsset/Tests/DcJsonFixture_Refs.json
{
    "$type" : "DcJsonAssetTestDataAsset3_References",
    "DirectRef1" : "Blueprint'/DcJsonAsset/DcFixture/DcTestBPDataAsset.DcTestBPDataAsset'",
    "DirectRef2" : "/DcJsonAsset/DcFixture/DcTestBPDataAsset",
    "DirectRef3" : {
        "$type" : "Blueprint",
        "$path" : "/DcJsonAsset/DcFixture/DcTestBPDataAsset"
    },
    "DcSoftImported1" : "/DcJsonAsset/DcFixture/DcTestBPDataAssetInstance",
    "DcSoftImported2" : null
}

Note that:

  1. DirectRef1/2/3 are deserialized into identical references to the same object.
  2. DcSoftImported1/2 are specially handled and /Path/To/Content are the only format that's supported.

Inline Sub Objects

UE already supports "Inline Sub Objects", that is UCLASS marked with DefaultToInstanced specifier. Editor would try to create new sub objects inline for these classes' references, rather than points to a existing one. It's also a way of doing data polymorphism in UE.

Given a simple class hierarchy like this:

// DcJsonAssetTests/Private/DcTestImports2.h
UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
class UDcJsonAsset_BaseShape : public UObject 
	// ...
    UPROPERTY(EditAnywhere) FName ShapeName;
};

UCLASS()
class UDcJsonAsset_ShapeBox : public UDcJsonAsset_BaseShape 
	// ...
    UPROPERTY(EditAnywhere) float Height;
	UPROPERTY(EditAnywhere) float Width;
};

UCLASS()
class UDcJsonAsset_ShapeSquare : public UDcJsonAsset_BaseShape 
    // ...
	UPROPERTY(EditAnywhere) float Radius;
};

An asset can be loaded from a JSON like this:

// DcJsonAssetTests/Private/DcTestImports2.h
UCLASS()
class UDcJsonAssetTestDataAsset4_SubObjects : public UDcImportedDataAsset {
	UPROPERTY(EditAnywhere) UDcJsonAsset_BaseShape* ShapeField1;
	UPROPERTY(EditAnywhere) UDcJsonAsset_BaseShape* ShapeField2;
	UPROPERTY(EditAnywhere) UDcJsonAsset_BaseShape* ShapeField3;
};

// DcJsonAsset/Tests/DcJsonFixture_SubObjects.json
{
    "$type" : "DcJsonAssetTestDataAsset4_SubObjects",
    "ShapeField1" : {
        "$type" : "DcJsonAsset_ShapeBox",
        "ShapeName" : "MyBox",
        "Height" : 43,
        "Width" : 25
    },
    "ShapeField2" : {
        "$type" : "DcJsonAsset_ShapeSquare",
        "Radius" : 16
    },
    "ShapeField3" : null
}

Tooling

The plugin also comes with a few supporting tooling and settings.

Settings

"DataConfig JsonAsset" settings can be found in "Project Settings" with the same name:

NameUsage
SuffixesThe file suffix that this plugin looks for. If you're already using .json suffix for other purposes you can add or change a new one.

Console Commands

We also provide some handy commands that can be used for batch processing.

CommandExampleUsage
Dc.ImportDirectoryDc.ImportDirectory "C:/Src/Dir" "/Game/Target"Recursively import source directory into content.
Dc.ReimportDirectoryDc.ReimportDirectory "/Game/Target"Recursively reimport content directory
Dc.SetupAutoReimportDc.SetupAutoReimport "C:/Src/Dir" "/Game/Target"Setup Auto reimport entry in editor settings

Tool UI

Finally we wrapped the commands above into a tooling panel, which can be found at "Windows -> JsonAsset Tool" menu:

JsonAssetUI

Caveats

This page put together miscellaneous topics.

Unicode Handling

We decided to use FFileHelper::LoadFileToString to load files into FString instances, which handles the following encodings:

  • UTF8 without BOM
  • UTF8 with BOM
  • UTF16 LE with BOM
  • UTF16 BE with BOM

If you can choose an encoding we recommend use UTF8 without BOM across the board. You can setup an .editorconfigto enforce it.

Changes

All notable changes to this project will be documented in this file.

Version listed here should match with VersionName field in DcJsonAsset.uplugin

1.1 - 2021-4-24

  • Integrate DataConfig Core 1.1.0
  • Fix UTF8 / UTF16 JSON file importing. Previously non ASCII characters are loaded as ?