ScriptableObject

This is basically a set of data as a unity asset. It can be created just like any other unity asset. The advantage is that you're not storing unnecessary data or duplicate data.

Create it like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName="New Item", menuName="Item")]
public class Item : ScriptableObject
{
    public string itemName;
    public int itemLevel;
    public Texture2D itemIcon;
}

And use it like this

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemHolder : MonoBehaviour
{
    public Item itemToHold;

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("I am holding {itemToHold.name}");        
    }
}

Saving

SO's can't be saved during runtime outside of the editor!

Recommended Mode: To serialize data you should use SerializedObject and SerializedProperty. This functionalities make sure you have a prober undo-state and flush all changes out. Example for Arrays:

[CreateAssetMenu(fileName = "SerializedArrayTester", menuName = "DEBUG")]
public class SerializedArrayTester : ScriptableObject
{
    [SerializeField]
    public float[] speed = {0f, 1f, 2f, 3f};
}

[CustomEditor(typeof(SerializedArrayTester))]
public class SerializedArrayTesterEditor : Editor
{
    SerializedProperty m_Speed;

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        m_Speed = serializedObject.FindProperty("speed");
        for (int x = 0; x < m_Speed.arraySize; x++) 
        {
            // get array element at x
            SerializedProperty property = m_Speed.GetArrayElementAtIndex(x); 
            // Edit this element's value.
            // in this case limit the float's value to a positive value.
            property.floatValue = Mathf.Max (0,property.floatValue); 
        }
        // draw property with it's children
        EditorGUILayout.PropertyField(m_Speed,true);
        serializedObject.ApplyModifiedProperties();
    }
}

Dirty Mode: Add an Undo.RecordObject action before changing any values. This will mark everything dirty and add an undo state:

EditorGUI.BeginChangeCheck();
float areaOfEffect = SomeAction();
if (EditorGUI.EndChangeCheck())
{
    // Like this:
    Undo.RecordObject(target, "Changed Area Of Effect");
    t.areaOfEffect = areaOfEffect;
}

Very dirty Mode: In editor mode you need to set it dirty and force the asset database to save all open files:

EditorUtility.SetDirty(theAsset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();