Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bobdebower's avatar

Casting not working please help!

i'am saving json to my database. and i wrote a code to coverting the table to xml. it dosn't covert the casts table to xml why?

here is my show here i convert the json to xml

  public function show(Configuration $configuration)
   {
    $data = Configuration::query()
        ->withCasts(['config' => 'json'])
        ->get()
        ->mapWithKeys(function ($configuration) {
            // to wrap each config object: <config>...</config>
            return ['config' => $configuration];
        });

    $result = ArrayToXml::convert(
        $data->jsonSerialize(), // convert collection to array
        'root', // root element name
        true, // replace spaces by underscore in element's names
        'UTF-8' // encoding
    );

    return response()->make($result, 200, ['Content-Type' => 'text/xml']);
 }

here is my. model

protected $casts = [

    // if its name was different you could use:
    // 'another_column' => 'json',
    'config' => 'json',
   ];

here is the output

<root>
<config>
   <id>1</id>
 <mac_address>1000</mac_address>
 <config>{ "config": { "version": 2.1, "general": { "test": "testdata", "testl": [ "test" ], "flood": } } } 
 </config>
 <created_at>2020-11-05T09:44:38.000000Z</created_at>
  <updated_at>2020-11-05T09:44:38.000000Z</updated_at>
  </config>
  </root>

here is my store controller

  public function store(Request $request)
  {

 $configuration = new Configuration();
$configuration->config = $request->config;
$configuration->mac_address = $request->mac_address;
$configurationSaved = $configuration->save();
if ($configurationSaved) {
    $response = ApiHelpers::apiResponse(false, 200, 'record saved successfully', null);
    return response()->json($response, 200);
} else {
    $response = ApiHelpers::apiResponse(true, 400, 'record saving failed', null);
    return response()->json($response, 400);
}

}
0 likes
30 replies
bobdebower's avatar

Yeah thanks. but still weird why my casts is not converting to xml. any idea?

automica's avatar

@laradoel what are you trying do? convert the inners of:

 <config>{ "config": { "version": 2.1, "general": { "test": "testdata", "testl": [ "test" ], "flood": } } } 
 </config>

to xml too?

1 like
bobdebower's avatar

yes. i want all the array attribute to xml so

  <version>2.1<version>
    <test> teststdata<test>

etc , this should work with casting i thought

automica's avatar

@laradoel but in your $casts you are telling Laravel to use json?

BTW I think you want to have a look at the xml you are producing. Currently you have a config which has a nested config.

You would be better to change the outer wrapper so you get:

<root>
    <configuration>
        <id>1</id>
        <mac_address>1000</mac_address>
        <config>
            <version>2.1</version>
            <test> teststdata</test>
        </config>
        <created_at>2020-11-05T09:44:38.000000Z</created_at>
        <updated_at>2020-11-05T09:44:38.000000Z</updated_at>
    </configuration>
</root>
1 like
bobdebower's avatar

Ok thanks, but it doesn't matter if i change the cast to array stil the same output

 protected $casts = [

    // if its name was different you could use:
    // 'another_column' => 'json',
    'config' => 'array',
  ];
<root>
<configuration>
    <id>1</id>
    <mac_address>40099</mac_address>
    <config>{
    "config": {
  "version": 2.1,
  "general": {
  "test": "true",
   }</config>
    <created_at>2020-11-05T13:09:47.000000Z</created_at>
    <updated_at>2020-11-05T13:09:47.000000Z</updated_at>
   </configuration>
 </root>

config is the table in the database saved as json

$table->json('config'); $table->string('mac_address');

automica's avatar

@laradoel you are fine to keep storing the config as json. you only need it as xml when you render the configuration. We can work that bit out next.

automica's avatar

@laradoel BTW can you manually edit below and get the xml to exactly how you want it and then we'll look at how to convert the json to match that.

<root>
    <configuration>
        <id>1</id>
        <mac_address>1000</mac_address>
        <config>
            <version>2.1</version>
            <test> teststdata</test>
	// need to add extra bits here
        </config>
        <created_at>2020-11-05T09:44:38.000000Z</created_at>
        <updated_at>2020-11-05T09:44:38.000000Z</updated_at>
    </configuration>
</root>
bobdebower's avatar

Thankyou for you respond!

so here is a better example. here is what i save in the database doing a dd($data)

Illuminate\Database\Eloquent\Collection {#1212 ▼
#items: array:1 [▼
"config" => App\Models\Configuration {#1230 ▼
#fillable: array:2 [▶]
#casts: array:1 [▼
"json" => "json"
]
#connection: "mysql"
#table: "configurations"
 #primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
 #withCount: []
 #perPage: 15
+exists: true
 +wasRecentlyCreated: false
 #attributes: array:5 [▼
  "id" => 1
  "mac_address" => "10000"
  "json" => ""{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse 
 kicking 
  asses since 1940\",\n  \"password\": \"noneed\"\n} ▶"
   "created_at" => "2020-11-06 09:25:40"
   "updated_at" => "2020-11-06 09:25:40"
     ]

Here is the xml output now.

 <root>
   <config>
     <id>1</id>
        <mac_address>10000</mac_address>
         <json>{ "firstName": "Chuck", "lastName": "Norris", "age": 75, "bio": "Roundhouse kicking asses since 
            1940", 
               "password": "noneed" }</json>
                  <created_at>2020-11-06T09:25:40.000000Z</created_at>
                     <updated_at>2020-11-06T09:25:40.000000Z</updated_at>
                       </config>
            </root>

This how i actually want the xml to look like

   <configuration>
     <id>1</id>
        <mac_address>10000</mac_address>
        <config>
          <firstname>Chuck</firstname>
         <lastname>Chuck</lastname>
           <age>75</age>
         <bio>Roundhouse kicking asses since 
              1940</bio>
           <password>noneed</password>
              </config>
                  <created_at>2020-11-06T09:25:40.000000Z</created_at>
                     <updated_at>2020-11-06T09:25:40.000000Z</updated_at>
                </configuration>

So with json i save ( $table->json('json'); ). i want the input data separated elements in de xml. in my model i use protected $casts = [ 'json' => 'json', ];

Here is my show controller converting it to xml.

public function show(Configuration $configuration)
  {
$data = Configuration::query()
    ->withCasts(['json' => 'json'])
    ->get()
    ->mapWithKeys(function ($configuration) {
        // replace config with the element you want
        // to wrap each config object: <config>...</config>
        return ['config' => $configuration];
    });

$result = ArrayToXml::convert(
    $data->jsonSerialize(), // convert collection to array
    'root', // root element name
    true, // replace spaces by underscore in element's names
    'UTF-8' // encoding
);

  return response()->make($result, 200, ['Content-Type' => 'text/xml']);
  }
automica's avatar

@laradoel ok. so if you adjust to this:

public function show(Configuration $configuration)
{
    $data = Configuration::query()
        ->withCasts(['json' => 'array'])
        ->get()
        ->mapWithKeys(function ($configuration) {
            // replace config with the element you want
            // to wrap each config object: <config>...</config>
            return ['configuration' => $configuration];
        });
    
    dd($data);

    $result = ArrayToXml::convert(
        $data->jsonSerialize(), // convert collection to array
        'root', // root element name
        true, // replace spaces by underscore in element's names
        'UTF-8' // encoding
    );

    return response()->make($result, 200, ['Content-Type' => 'text/xml']);
}

show me what is returned by dd();

NB I changed this to be array

   ->withCasts(['json' => 'array'])

also, I think it would wise to rename the column to desctribe whats in it, eg 'settings' or 'data' rather than naming it based on the type of content eg 'json'.

bobdebower's avatar

Here is a dd($result)

<root>
 <configuration>
   <id>1</id>
    <mac_address>10000</mac_address>
   <json>{
     "firstName": "Chuck",
     "lastName": "Norris",
      "age": 75,
     "bio": "Roundhouse kicking asses since 1940",
      "password": "noneed"
        }
     </json>
        <created_at>2020-11-06T09:25:40.000000Z</created_at>
           <updated_at>2020-11-06T09:25:40.000000Z</updated_at>
       </configuration>
         </root>

So the edited configuration element worked fine! but 'json' is still in array instead of xml and also how to get rid of the root element?

bobdebower's avatar

Okay, thanks,

I have renamed the column 'data' and changed ->withCasts(['data' => 'array'])

Here is de dd($data)

 #items: array:1 [▼
  "configuration" => App\Models\Configuration {#1230 ▼
  #fillable: array:2 [▶]
  #casts: array:1 [▼
    "data" => "array"
  ]
  #connection: "mysql"
  #table: "configurations"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:5 [▼
    "id" => 1
    "mac_address" => "10000"
    "data" => ""{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse 
     kicking asses since 1940\",\n  \"password\": \"noneed\"\n} ▶"
    "created_at" => "2020-11-06 10:41:18"
    "updated_at" => "2020-11-06 10:41:18"
     ]
     #original: array:5 [▼
       "id" => 1
      "mac_address" => "10000"
    "data" => ""{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse 
    kicking asses since 1940\",\n  \"password\": \"noneed\"\n} ▶"
    "created_at" => "2020-11-06 10:41:18"
    "updated_at" => "2020-11-06 10:41:18"
     ]
automica's avatar

@laradoel ok in your model can you add or update

$casts = ['data' => 'array'];

and show dd(data) again?

bobdebower's avatar

Sure after updating the casts in model to 'data' => 'array',

Illuminate\Database\Eloquent\Collection {#1212 ▼
 #items: array:1 [▼
"configuration" => App\Models\Configuration {#1230 ▼
  #fillable: array:2 [▼
    0 => "data"
    1 => "mac_address"
  ]
  #casts: array:1 [▼
    "data" => "array"
  ]
  #connection: "mysql"
  #table: "configurations"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:5 [▼
    "id" => 1
    "mac_address" => "10000"
    "data" => ""{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse kicking asses since 1940\",\n  \"password\": \"noneed\"\n} ▶"
    "created_at" => "2020-11-06 10:41:18"
    "updated_at" => "2020-11-06 10:41:18"
  ]
  #original: array:5 [▼
    "id" => 1
    "mac_address" => "10000"
    "data" => ""{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse kicking asses since 1940\",\n  \"password\": \"noneed\"\n} ▶"
    "created_at" => "2020-11-06 10:41:18"
    "updated_at" => "2020-11-06 10:41:18"
  ]
automica's avatar

@laradoel ah. I think you also need to adjust your migration to switch 'data' column to json.

  $table->json('data')->nullable();
bobdebower's avatar

Ok already had $table->json('data')->nullable();

But i just discoverd something weird.

i use postman for the post request.

 <data>{
   "firstName": "Chuck",
   "lastName": "Norris",
   "age": 75,
   "bio": "Roundhouse kicking asses since 1940",
   "password": "noneed"
     }
 </data>

But i just tried manually inserting the data in the database just with the same array. and it worked!

  <root>
    <configuration>
     <id>2</id>
     <mac_address>20000</mac_address>
    <data>
   <age>75</age>
   <bio>Roundhouse kicking asses since 1940</bio>
   <lastName>Norris</lastName>
  <password>noneed</password>
   <firstName>Chuck</firstName>
 </data>

so i think maybe something is wrong with my store function here it is. in postman i just use post request with key and the value for the formdata

public function store(Request $request)
  {

    $configuration = new Configuration();
    $configuration->data= $request->data;
    $configuration->mac_address = $request->mac_address;
    $configurationSaved = $configuration->save();
    if ($configurationSaved) {
        $response = ApiHelpers::apiResponse(false, 201, 'record saved successfully', null);
        return response()->json($response, 201);
    } else {
        $response = ApiHelpers::apiResponse(true, 400, 'record saving failed', null);
        return response()->json($response, 400);
    }

}
automica's avatar

@laradoel can you export the request you are sending from postman so I can see if its a postman issue or with your store?

bobdebower's avatar

do you mean this?

 {
"info": {
	"_postman_id": "7944364a-119b-4351-91a3-e8921fe4eae1",
	"name": "config",
	"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
	{
		"name": "http://127.0.0.1:8000/api/configurations",
		"request": {
			"method": "POST",
			"header": [
				{
					"key": "Content-Type",
					"value": "application/json",
					"type": "text"
				}
			],
			"body": {
				"mode": "formdata",
				"formdata": [
					{
						"key": "mac_address",
						"value": "10000",
						"type": "text"
					},
					{
						"key": "data",
						"value": "{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse kicking asses since 1940\",\n  \"password\": \"noneed\"\n}",
						"type": "text"
					}
				]
			},
			"url": {
				"raw": "http://127.0.0.1:8000/api/configurations",
				"protocol": "http",
				"host": [
					"127",
					"0",
					"0",
					"1"
				],
				"port": "8000",
				"path": [
					"api",
					"configurations"
				]
			},
			"description": "posting new configration"
		},
		"response": []
	},
	{
		"name": "http://127.0.0.1:8000/api/configurations",
		"protocolProfileBehavior": {
			"disableBodyPruning": true
		},
		"request": {
			"auth": {
				"type": "apikey"
			},
			"method": "GET",
			"header": [],
			"body": {
				"mode": "urlencoded",
				"urlencoded": []
			},
			"url": {
				"raw": "http://127.0.0.1:8000/api/configurations",
				"protocol": "http",
				"host": [
					"127",
					"0",
					"0",
					"1"
				],
				"port": "8000",
				"path": [
					"api",
					"configurations"
				]
			},
			"description": "get all configurations"
		},
		"response": []
	}
],
"variable": [
	{
		"id": "baseUrl",
		"key": "baseUrl",
		"value": "http://localhost:3000",
		"type": "string"
	}
],
"protocolProfileBehavior": {}
 }
bobdebower's avatar

@automica this is for only the post request

{
"info": {
	"_postman_id": "7944364a-119b-4351-91a3-e8921fe4eae1",
	"name": "config",
	"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
	{
		"name": "http://127.0.0.1:8000/api/configurations",
		"request": {
			"method": "POST",
			"header": [
				{
					"key": "Content-Type",
					"value": "application/json",
					"type": "text"
				}
			],
			"body": {
				"mode": "formdata",
				"formdata": [
					{
						"key": "mac_address",
						"value": "10000",
						"type": "text"
					},
					{
						"key": "data",
						"value": "{\n  \"firstName\": \"Chuck\",\n  \"lastName\": \"Norris\",\n  \"age\": 75,\n  \"bio\": \"Roundhouse kicking asses since 1940\",\n  \"password\": \"noneed\"\n}",
						"type": "text"
					}
				]
			},
			"url": {
				"raw": "http://127.0.0.1:8000/api/configurations",
				"protocol": "http",
				"host": [
					"127",
					"0",
					"0",
					"1"
				],
				"port": "8000",
				"path": [
					"api",
					"configurations"
				]
			},
			"description": "posting new configration"
		},
		"response": []
	}
],
"variable": [
	{
		"id": "baseUrl",
		"key": "baseUrl",
		"value": "http://localhost:3000",
		"type": "string"
	}
],
"protocolProfileBehavior": {}
  }
automica's avatar
automica
Best Answer
Level 54

@laradoel looks like you just need to tweak your data

 $configuration->data = json_decode($request->data);
bobdebower's avatar

Finally!!!!!! THANKYOU SO much! is it possible to get rid of the root element or just keep it?

     <root>
        < configuration>
        <id>2</id>
        <mac_address>200000</mac_address>
        <data>
         <age>75</age>
         <bio>Roundhouse kicking asses since 1940</bio>
        <lastName>Norris</lastName>
        <password>noneed</password>
        <firstName>Chuck</firstName>
       </data>
      <created_at>2020-11-06T12:12:29.000000Z</created_at>
      <updated_at>2020-11-06T12:12:29.000000Z</updated_at>
        </configuration>
    </root>
automica's avatar

@laradoel you do need a root element but it doesn't need to be root. Removing the mapWithKeys and renaming root to 'configuration' should do it.

public function show(Configuration $configuration)
{
    $data = Configuration::query()
        ->withCasts(['json' => 'array'])
        ->get()
  	 ->mapWithKeys(function ($configuration) {
            return ['configuration' => $configuration];
        });
   
    $result = ArrayToXml::convert(
        $data->jsonSerialize(), // convert collection to array
        'configuration', // root element name
        true, // replace spaces by underscore in element's names
        'UTF-8' // encoding
    );

    return response()->make($result, 200, ['Content-Type' => 'text/xml']);
}
bobdebower's avatar

Thanks!

I think i need the mapping because i recieved back the error DOMException Invalid Character Error

from the array to xml package

      if ($this->isArrayAllKeySequential($array) && ! empty($array)) {

        throw new DOMException('Invalid Character Error');

    }

highlighting below in the show function

'UTF-8' // encoding

    );
bobdebower's avatar

Ok thanks, almost there!! recieve this back

   <configuration>
    <incrementing>1</incrementing>
      <exists>1</exists>
       <wasRecentlyCreated/>
       <timestamps>1</timestamps>
   </configuration>
automica's avatar

@laradoel I guess we're back to having

 ->mapWithKeys(function ($configuration) {
            // replace config with the element you want
            // to wrap each config object: <config>...</config>
            return ['configuration' => $configuration];
        });

BTW as withCasts didn't seem to do anything, do you get an object if you do:

$data = Configuration::query() ->get();

but then we've already got the object, so can we reduce your method down to:

public function show(Configuration $configuration)
{
    $result = ArrayToXml::convert(
        $configuration->jsonSerialize(), // convert collection to array
        'configuration', // root element name
        true, // replace spaces by underscore in element's names
        'UTF-8' // encoding
    );

    return response()->make($result, 200, ['Content-Type' => 'text/xml']);
1 like
bobdebower's avatar

worked! Yes thats it! Thakyou very much for helpen me. have a good day!

1 like
bobdebower's avatar

Hello, i have a new probleme. esay but can't figure it out. i use pluck but i got this error. Invalid Character Error

i want to convert json to xml but only the table 'data' iam saving json in 'data' table. but i only want the the 'data' to convert to xml

public function show(Configuration $configuration)
 {
    $result = ArrayToXml::convert(
        $configuration::all()->pluck('data')->jsonSerialize(), // convert collection to array
        'config', // root element name
        true, // replace spaces by underscore in element's names
        'UTF-8' // encoding
    );

    return response()->make($result, 200, ['Content-Type' => 'text/xml']);
  }

Here is my model

class Configuration extends Model
 {
 use HasFactory;

protected $fillable = [
    'data'
];

public $timestamps = false;

protected $casts = [

    'data' => 'array',
];

public function getLinkAttribute()
{
    return '/api/configurations/' . $this->id;
}

 }

Here is my migration

public function up()
   {
    Schema::create('configurations', function (Blueprint $table) {
        $table->id();
        $table->json('data')->nullable();
    });

  }

Please or to participate in this conversation.