Decode org.apache.kafka.connect.data.Decimal in typescript - avro

In our Avro scheme we have following 2 decimal types
{
"name": "LONG",
"type": {
"type": "bytes",
"scale": 12,
"precision": 64,
"connect.doc": "Long",
"connect.version": 1,
"connect.parameters": {
"scale": "12",
"jcoType": "JCoBCDType",
"length": "9",
"decimals": "12"
},
"connect.default": "\u0000",
"connect.name": "org.apache.kafka.connect.data.Decimal",
"logicalType": "decimal"
},
"default": "\u0000"
},
{
"name": "LONG2",
"type": {
"type": "bytes",
"scale": 12,
"precision": 64,
"connect.doc": "Long2",
"connect.version": 1,
"connect.parameters": {
"scale": "12",
"jcoType": "JCoBCDType",
"length": "9",
"decimals": "12"
},
"connect.default": "\u0000",
"connect.name": "org.apache.kafka.connect.data.Decimal",
"logicalType": "decimal"
},
"default": "\u0000"
}
Example for:
LONG : 55.71364,
LONG2: 12.43337,
The Kafka broker sends the following encoded message (snippet)
"\f2�׸�\x00\f\x0BNޚ*�\x00"
On our typescript backend we then try to decode it.
import {SchemaRegistry} from '#kafkajs/confluent-schema-registry';
import {AvroDecimal} from '#ovotech/avro-decimal';
const registry = new SchemaRegistry({
host: appConfig.kafkaSchemaUrl,
auth: {
username: appConfig.kafkaUser,
password: appConfig.kafkaPassword
}
}, { forSchemaOptions: { logicalTypes: { 'decimal': AvroDecimal }}}
);
const buffer: Buffer = Buffer.from(message);
const decoded = await registry.decode(buffer);
console.log(decoded);
It fails with "Error: expecting underlying Buffer type".
Other attempts:
const buffer = atob(message);
const buffer = Buffer.from(message, 'base64');
const buffer = Buffer.from(message, 'binary');
const buffer = Buffer.from(message, 'ascii');
Also tried to extend the AvroDecimal class.
Tried to skip the decoding of the decimal, by creating a resolver.
Accepted Solution:
Decimal type could be decoded.
Solution is in ts or js.
Altering the scheme is not an option.

Related

Correct format for decimal and timestamp-micros

I am trying to find the correct AVRO Schema format to define decimal and timestamp-micros logical types?
Whether NumericField1/ DateFieldA or NumericField2/DateFieldB is correct format for the Logical Type?
{
"type": "record",
"name": "DateAndNumber",
"namespace": "org.sample",
"fields": [
{
"name": "NumericField1",
"type": [
"null",
"bytes"
],
"default": null,
"locgicalType": "decimal",
"precision": 8,
"scale": 2
},
{
"name": "NumericField2",
"type": [
"null",
{
"type": "bytes",
"logicalType": "decimal",
"precision": 4,
"scale": 2
}
],
"default": null
},
{
"name": "DateFieldA",
"type": "long",
"logicalType": "timestamp-micros",
"default": 0
},
{
"name": "DateFieldB",
"type": {
"type": "long",
"logicalType": "timestamp-micros"
},
"default": 0
}
]
}

confluent_kafka.error.ValueSerializationError: KafkaError{code=_VALUE_SERIALIZATION,val=-161 : ValueError

I am new bee to python and trying to use 'confluent_kafka' for avro message produce.
Using 'confluent_kafka.schema_registry.avro.AvroSerializer' for the same
(referred : https://github.com/confluentinc/confluent-kafka-python/blob/master/examples/avro_producer.py)
It works for simple avro schema with dict(json converted to dict) input, but for below sample schema I am getting error :
Schema :
{
"type": "record",
"name": "Envelope",
"namespace": "CoreOLTPEvents.dbo.Event",
"fields": [{
"name": "before",
"type": ["null", {
"type": "record",
"name": "Value",
"fields": [{
"name": "EventId",
"type": "long"
}, {
"name": "CameraId",
"type": ["null", "long"],
"default": null
}],
"connect.name": "CoreOLTPEvents.dbo.Event.Value"
}],
"default": null
}, {
"name": "after",
"type": ["null", "Value"],
"default": null
}, {
"name": "source",
"type": {
"type": "record",
"name": "Source",
"namespace": "io.debezium.connector.sqlserver",
"fields": [{
"name": "version",
"type": "string"
}, {
"name": "connector",
"type": "string"
}],
"connect.name": "io.debezium.connector.sqlserver.Source"
}
}, {
"name": "op",
"type": "string"
}],
"connect.name": "CoreOLTPEvents.dbo.Event.Envelope"
}
Input Json :
{
"after": null,
"before": {
"CoreOLTPEvents.dbo.Event.Value" : {
"EventId": 1111111111,
"CameraId": 222222222
}
},
"source": {
"version": "InitialLoad",
"connector": "sqlserver"
},
"op": "C"
}
Error :
ValueError: {'CoreOLTPEvents.dbo.Event.Value': {'EventId': 1111111111, 'CameraId': 222222222}} (type <class 'dict'>) do not match ['null', {'connect.name': 'CoreOLTPEvents.dbo.Event.Value', 'type': 'record', 'name': 'CoreOLTPEvents.dbo.Event.Value', 'fields': [{'name': 'EventId', 'type': 'long'}, {'default': None, 'name': 'CameraId', 'type': ['null', 'long']}]}] on field before
'before' field type is union (['null',record]), if I change it to only record (remove union) then it works fine.
But I need to adjust my input such a way that it works for given schema.
(Note : I am reading json input using 'json.load(json_file)' so it gives dict output)
Any help would be much appreciated.
Update :
Actual large schema :
{
"type": "record",
"name": "Envelope",
"namespace": "CoreOLTPEvents.dbo.Event",
"fields": [{
"name": "before",
"type": ["null", {
"type": "record",
"name": "Value",
"fields": [{
"name": "EventId",
"type": "long"
}, {
"name": "CameraId",
"type": ["null", "long"],
"default": null
}, {
"name": "SiteId",
"type": ["null", "long"],
"default": null
}, {
"name": "VehicleId",
"type": ["null", "long"],
"default": null
}, {
"name": "EventReviewStatusID",
"type": "int"
}, {
"name": "EventTypeId",
"type": ["null", "int"],
"default": null
}, {
"name": "EventDateTime",
"type": ["null", {
"type": "string",
"connect.name": "net.smartdrive.converters.SmartdriveEventDateFieldConverter"
}],
"default": null
}, {
"name": "FTPUploadDateTime",
"type": {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}
}, {
"name": "CAMFileName",
"type": "string"
}, {
"name": "KeypadEntryCode",
"type": ["null", "string"],
"default": null
}, {
"name": "IsActive",
"type": {
"type": "boolean",
"connect.default": true
},
"default": true
}, {
"name": "Flagged",
"type": "boolean"
}, {
"name": "EventTitle",
"type": ["null", "string"],
"default": null
}, {
"name": "CreatedBy",
"type": "long"
}, {
"name": "CreatedDate",
"type": {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}
}, {
"name": "ModifiedBy",
"type": "long"
}, {
"name": "ModifiedDate",
"type": {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}
}, {
"name": "ReReviewAnalysis",
"type": ["null", "string"],
"default": null
}, {
"name": "LegacyEventId",
"type": ["null", "long"],
"default": null
}, {
"name": "TripId",
"type": ["null", "long"],
"default": null
}, {
"name": "FileVersion",
"type": ["null", "string"],
"default": null
}, {
"name": "EventNumber",
"type": ["null", "string"],
"default": null
}, {
"name": "Latitude",
"type": ["null", {
"type": "bytes",
"scale": 10,
"precision": 13,
"connect.version": 1,
"connect.parameters": {
"scale": "10",
"connect.decimal.precision": "13"
},
"connect.name": "org.apache.kafka.connect.data.Decimal",
"logicalType": "decimal"
}],
"default": null
}, {
"name": "Longitude",
"type": ["null", {
"type": "bytes",
"scale": 10,
"precision": 13,
"connect.version": 1,
"connect.parameters": {
"scale": "10",
"connect.decimal.precision": "13"
},
"connect.name": "org.apache.kafka.connect.data.Decimal",
"logicalType": "decimal"
}],
"default": null
}, {
"name": "GeoAddressId",
"type": ["null", "long"],
"default": null
}, {
"name": "ReviewedEventId",
"type": ["null", "long"],
"default": null
}, {
"name": "VideoStatus",
"type": {
"type": "int",
"connect.default": 0
},
"default": 0
}, {
"name": "PredictionImportance",
"type": ["null", {
"type": "bytes",
"scale": 10,
"precision": 15,
"connect.version": 1,
"connect.parameters": {
"scale": "10",
"connect.decimal.precision": "15"
},
"connect.name": "org.apache.kafka.connect.data.Decimal",
"logicalType": "decimal"
}],
"default": null
}, {
"name": "FlaggedBy",
"type": ["null", "long"],
"default": null
}, {
"name": "FlaggedDate",
"type": ["null", {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}],
"default": null
}, {
"name": "TriggerTypeId",
"type": ["null", "int"],
"default": null
}, {
"name": "VideoDeleteDate",
"type": ["null", {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}],
"default": null
}, {
"name": "MetadataDeleteDate",
"type": ["null", {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}],
"default": null
}, {
"name": "RetentionStatus",
"type": {
"type": "int",
"connect.default": 0,
"connect.type": "int16"
},
"default": 0
}, {
"name": "PartnerTriggerId",
"type": ["null", "int"],
"default": null
}, {
"name": "CoachingStateId",
"type": {
"type": "int",
"connect.default": 0,
"connect.type": "int16"
},
"default": 0
}, {
"name": "EventKudoHistoryId",
"type": ["null", "int"],
"default": null
}],
"connect.name": "CoreOLTPEvents.dbo.Event.Value"
}],
"default": null
}, {
"name": "after",
"type": ["null", "Value"],
"default": null
}, {
"name": "source",
"type": {
"type": "record",
"name": "Source",
"namespace": "io.debezium.connector.sqlserver",
"fields": [{
"name": "version",
"type": "string"
}, {
"name": "connector",
"type": "string"
}, {
"name": "name",
"type": "string"
}, {
"name": "ts_ms",
"type": "long"
}, {
"name": "snapshot",
"type": [{
"type": "string",
"connect.version": 1,
"connect.parameters": {
"allowed": "true,last,false"
},
"connect.default": "false",
"connect.name": "io.debezium.data.Enum"
}, "null"],
"default": "false"
}, {
"name": "db",
"type": "string"
}, {
"name": "schema",
"type": "string"
}, {
"name": "table",
"type": "string"
}, {
"name": "change_lsn",
"type": ["null", "string"],
"default": null
}, {
"name": "commit_lsn",
"type": ["null", "string"],
"default": null
}, {
"name": "event_serial_no",
"type": ["null", "long"],
"default": null
}],
"connect.name": "io.debezium.connector.sqlserver.Source"
}
}, {
"name": "op",
"type": "string"
}, {
"name": "ts_ms",
"type": ["null", "long"],
"default": null
}, {
"name": "transaction",
"type": ["null", {
"type": "record",
"name": "ConnectDefault",
"namespace": "io.confluent.connect.avro",
"fields": [{
"name": "id",
"type": "string"
}, {
"name": "total_order",
"type": "long"
}, {
"name": "data_collection_order",
"type": "long"
}]
}],
"default": null
}],
"connect.name": "CoreOLTPEvents.dbo.Event.Envelope"
}
Input for large schema :
{
"before": null,
"after": {
"EventId": 1234566,
"CameraId": 2233,
"SiteId": 111,
"VehicleId": 45587,
"EventReviewStatusID": 10,
"EventTypeId": 123,
"EventDateTime": "2015-01-02T01:30:29Z",
"FTPUploadDateTime": 1420193330590,
"CAMFileName": "XYZ",
"KeypadEntryCode": "0",
"IsActive": false,
"Flagged": false,
"EventTitle": null,
"CreatedBy": 1,
"CreatedDate": 1420191120730,
"ModifiedBy": 1,
"ModifiedDate": 1577871185680,
"ReReviewAnalysis": null,
"LegacyEventId": null,
"TripId": 3382,
"FileVersion": "2.2",
"EventNumber": "AAAA-BBBB",
"Latitude": "UU9elrA=",
"Longitude": "/ueZUeFw",
"GeoAddressId": null,
"ReviewedEventId": 129411077,
"VideoStatus": 4,
"PredictionImportance": 0.1402457539,
"FlaggedBy": null,
"FlaggedDate": null,
"TriggerTypeId": 322,
"VideoDeleteDate": 1422783120000,
"MetadataDeleteDate": 1577871120000,
"RetentionStatus": 15,
"PartnerTriggerId": null,
"CoachingStateId": 0,
"EventKudoHistoryId": null
},
"source": {
"version": "Final",
"connector": "sqlserver",
"name": "CoreOLTP",
"ts_ms": 1615813992548,
"snapshot": "false",
"db": "CoreOLTP",
"schema": "dbo",
"table": "xyz",
"change_lsn": null,
"commit_lsn": null,
"event_serial_no": null
},
"op": "C",
"ts_ms": 1615813992548,
"transaction": null
}
Error :
confluent_kafka.error.ValueSerializationError: KafkaError{code=_VALUE_SERIALIZATION,val=-161,str="{'EventId': 129411077, 'CameraId': 46237, 'SiteId': 2148, 'VehicleId': 45587, 'EventReviewStatusID': 10, 'EventTypeId': 247, 'EventDateTime': '2015-01-02T01:30:29Z', 'FTPUploadDateTime': 1420191120590, 'CAMFileName': 'JD2BC02120150102013029ER.SDE', 'KeypadEntryCode': '0', 'IsActive': False, 'Flagged': False, 'EventTitle': None, 'CreatedBy': 1, 'CreatedDate': 1420191120730, 'ModifiedBy': 1, 'ModifiedDate': 1577871185680, 'ReReviewAnalysis': None, 'LegacyEventId': None, 'TripId': 3382, 'FileVersion': '2.2', 'EventNumber': 'WSHX-8QQ2', 'Latitude': 'UU9elrA=', 'Longitude': '/ueZUeFw', 'GeoAddressId': None, 'ReviewedEventId': 129411077, 'VideoStatus': 4, 'PredictionImportance': 0.1402457539, 'FlaggedBy': None, 'FlaggedDate': None, 'TriggerTypeId': 322, 'VideoDeleteDate': 1422783120000, 'MetadataDeleteDate': 1577871120000, 'RetentionStatus': 15, 'PartnerTriggerId': None, 'CoachingStateId': 0, 'EventKudoHistoryId': None} (type <class 'dict'>) do not match ['null', 'CoreOLTPEvents.dbo.Event.Value'] on field after"}
You just need to change your input so that the before field doesn't have the namespace. So it needs to look like this:
{
"after": null,
"before": {
"EventId": 1111111111,
"CameraId": 222222222
},
"source": {
"version": "InitialLoad",
"connector": "sqlserver"
},
"op": "C"
}
The original input you had looked like it was trying to be JSON encoded avro because the field before had the CoreOLTPEvents.dbo.Event.Value namespace. However, I'm guessing it must have been hand crafted because CameraId should have been specified as {"long": 222222222} rather than just 222222222.
If you do actually have Avro encoded JSON (from the result of some other process or something) then you you could use something like fastavro.json_reader to read in that file and it will create the correct memory representation (that doesn't include the type information for union fields).
UPDATE:
To figure out what the problem is with the full schema and full data, I first loaded the two objects using json.load and then used fastavro.validate(record, schema) The output from that is a stacktrace that ends with this:
fastavro._validate_common.ValidationError: [
"CoreOLTPEvents.dbo.Event.Envelope.after is <{'EventId': 1234566, 'CameraId': 2233, 'SiteId': 111, 'VehicleId': 45587, 'EventReviewStatusID': 10, 'EventTypeId': 123, 'EventDateTime': '2015-01-02T01:30:29Z', 'FTPUploadDateTime': 1420193330590, 'CAMFileName': 'XYZ', 'KeypadEntryCode': '0', 'IsActive': False, 'Flagged': False, 'EventTitle': None, 'CreatedBy': 1, 'CreatedDate': 1420191120730, 'ModifiedBy': 1, 'ModifiedDate': 1577871185680, 'ReReviewAnalysis': None, 'LegacyEventId': None, 'TripId': 3382, 'FileVersion': '2.2', 'EventNumber': 'AAAA-BBBB', 'Latitude': 'UU9elrA=', 'Longitude': '/ueZUeFw', 'GeoAddressId': None, 'ReviewedEventId': 129411077, 'VideoStatus': 4, 'PredictionImportance': 0.1402457539, 'FlaggedBy': None, 'FlaggedDate': None, 'TriggerTypeId': 322, 'VideoDeleteDate': 1422783120000, 'MetadataDeleteDate': 1577871120000, 'RetentionStatus': 15, 'PartnerTriggerId': None, 'CoachingStateId': 0, 'EventKudoHistoryId': None}> of type <class 'dict'> expected null",
"CoreOLTPEvents.dbo.Event.Value.Latitude is <UU9elrA=> of type <class 'str'> expected null",
"CoreOLTPEvents.dbo.Event.Value.Latitude is <UU9elrA=> of type <class 'str'> expected {'scale': 10, 'precision': 13, 'connect.version': 1, 'connect.parameters': {'scale': '10', 'connect.decimal.precision': '13'}, 'connect.name': 'org.apache.kafka.connect.data.Decimal', 'logicalType': 'decimal', 'type': 'bytes'}"
]
So that is trying to tell us that there is 3 potential problems. The first is that the value in after doesn't match null, but we can ignore that because we don't want after to match null.
The later two problems are the actual problem. It says that the value of Latitude is the string UU9elrA=, but that doesn't match either null or bytes. The string here looks base64 encoded, so maybe you have some code that decodes that to bytes and if so then maybe the actual problem is something else, but if so then I think you should be able to use fastavro.validate to figure out what the problem is.

Is there a way to enter dollars/cents via DTMF?

I'm currently working on a pay-by-phone system with this flow:
<Gather> customer's account number with us
<Gather> amount to pay
<Pay> - with the previous 2 <Gather>s passed as variables to Stripe
The issue I am having is collecting the amount. Is there a way to convert the amount from a single string to a decimal string?
eg. 12300 becomes 123.00
All suggestions welcome as the current working theory is to add a third <Gather> just for the cents but this feels cumbersome from a UX/UI perspective.
I edited this answer, since I was successful in accomplishing this.
I did it with STUDIO and FUNCTIONS.
Prompt the caller to enter the amount using star key () as a decimal. For example to enter $5.43 they should enter 543 and then press #.
then I took the gathered digits and sent them out as a parameter in the FUNCTION widget by inserting "amount" as the KEY and {{widgets.gather_1.digits}} as the VALUE of the parameter.
I wrote the following function:
exports.handler = function(context, event, callback) {
const rawamount = event.amount;
const decimalamount = rawamount.replace("*",".");
const response = decimalamount;
callback(null, response);
};
then in the <pay> widget I inserted {{widget.function_1.body}} in the AMOUNT field.
Your payment should process with dollars and cents.
Let me know if this worked. If you need screenshots of my studio flow let me know.
ADDED:
Here is a sample flow if someone wants to use as a template (someone requested it)
Open a blank Studio flow.
In the trigger widget under "config" tab click on "SHOW FLOW JSON" delete all previous code and paste code below inside and save.
Your flow should generate.
Then create function. Code and instructions below after JSON.
PLEASE NOTE: though a function may pop up in the flow, you won't be able to access it, and will have to create it under your account.
{
"description": "A New Flow",
"states": [
{
"name": "Trigger",
"type": "trigger",
"transitions": [
{
"event": "incomingMessage"
},
{
"next": "gather_1",
"event": "incomingCall"
},
{
"event": "incomingRequest"
}
],
"properties": {
"offset": {
"x": 0,
"y": 0
}
}
},
{
"name": "gather_1",
"type": "gather-input-on-call",
"transitions": [
{
"next": "function_1",
"event": "keypress"
},
{
"event": "speech"
},
{
"event": "timeout"
}
],
"properties": {
"voice": "man",
"speech_timeout": "auto",
"offset": {
"x": 30,
"y": 270
},
"loop": 1,
"finish_on_key": "#",
"say": "Please enter an amount, then press pound. To enter with cents use the star key. for example to enter $6.25 press six star two five.",
"language": "en-US",
"stop_gather": true,
"gather_language": "en",
"profanity_filter": "true",
"timeout": 5
}
},
{
"name": "function_1",
"type": "run-function",
"transitions": [
{
"next": "say_play_2",
"event": "success"
},
{
"event": "fail"
}
],
"properties": {
"offset": {
"x": -60,
"y": 530
},
"parameters": [
{
"value": "{{widgets.gather_1.Digits}}",
"key": "amount"
}
],
"url": "https://charcoal-sloth-2579.twil.io/insert-decimal"
}
},
{
"name": "say_play_2",
"type": "say-play",
"transitions": [
{
"next": "pay_1",
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -59,
"y": 839
},
"loop": 1,
"say": "You entered ${{widgets.function_1.body}}",
"language": "en-US"
}
},
{
"name": "pay_1",
"type": "capture-payments",
"transitions": [
{
"next": "say_play_3",
"event": "success"
},
{
"next": "say_play_4",
"event": "maxFailedAttempts"
},
{
"next": "say_play_4",
"event": "providerError"
},
{
"next": "say_play_4",
"event": "payInterrupted"
},
{
"next": "say_play_4",
"event": "hangup"
},
{
"next": "say_play_4",
"event": "validationError"
}
],
"properties": {
"security_code": true,
"offset": {
"x": -70,
"y": 1140
},
"max_attempts": 2,
"payment_connector": "Stripe_Connector",
"payment_amount": "{{widgets.function_1.body}}",
"currency": "usd",
"language": "en-US",
"postal_code": "false",
"payment_token_type": "one-time",
"timeout": 5,
"valid_card_types": [
"visa",
"master-card",
"amex",
"discover"
]
}
},
{
"name": "say_play_3",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": -185,
"y": 1433
},
"loop": 1,
"say": "Your payment ${{widgets.function_1.body}}was successful, Thank you.",
"language": "en-US"
}
},
{
"name": "say_play_4",
"type": "say-play",
"transitions": [
{
"event": "audioComplete"
}
],
"properties": {
"voice": "man",
"offset": {
"x": 190,
"y": 1456
},
"loop": 1,
"say": "There was an error with your payment. Goodbye!",
"language": "en-US"
}
}
],
"initial_state": "Trigger",
"flags": {
"allow_concurrent_calls": true
}
}
Create a function with this code:
exports.handler = function(context, event, callback) {
const amount = event.amount;
const convert = amount.replace('*', '.');
callback(null, convert);
};
Make sure in the flow you check that the function widget has the right function selected and that you insert the following parameter KEY:amount VALUE: {{widgets.YOUR_GATHER_WIDGET NAME.body}}

How to iterate through array to pull out properties for specifc keys?

Im working with decoded JSON, and specifically i need to work within an array to pull out a property of a specific object based on its key.
JSON looks like the following, and Im needing to pull out the filenames for "fanart" and "clearlogo"
{
"code": 200,
"status": "Success",
"data": {
"count": 1,
"base_url": {
"original": "https://cdn.thegamesdb.net/images/original/",
"small": "https://cdn.thegamesdb.net/images/small/",
"thumb": "https://cdn.thegamesdb.net/images/thumb/",
"cropped_center_thumb": "https://cdn.thegamesdb.net/images/cropped_center_thumb/",
"medium": "https://cdn.thegamesdb.net/images/medium/",
"large": "https://cdn.thegamesdb.net/images/large/"
},
"images": {
"2578": [
{
"id": 5512,
"type": "boxart",
"side": "front",
"filename": "boxart/front/2578-1.jpg",
"resolution": "1533x2155"
},
{
"id": 5513,
"type": "boxart",
"side": "back",
"filename": "boxart/back/2578-1.jpg",
"resolution": "1522x2155"
},
{
"id": 87092,
"type": "fanart",
"side": null,
"filename": "fanart/2578-1.jpg",
"resolution": "1920x1080"
},
{
"id": 87093,
"type": "fanart",
"side": null,
"filename": "fanart/2578-2.jpg",
"resolution": "1920x1080"
},
{
"id": 87094,
"type": "fanart",
"side": null,
"filename": "fanart/2578-3.jpg",
"resolution": "1920x1080"
},
{
"id": 87095,
"type": "clearlogo",
"side": null,
"filename": "clearlogo/2578.png",
"resolution": "400x300"
},
{
"id": 87096,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-1.jpg",
"resolution": null
},
{
"id": 87097,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-2.jpg",
"resolution": null
},
{
"id": 87098,
"type": "fanart",
"side": null,
"filename": "fanart/2578-4.jpg",
"resolution": "1920x1080"
},
{
"id": 87099,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-3.jpg",
"resolution": null
},
{
"id": 87100,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-4.jpg",
"resolution": null
},
{
"id": 87101,
"type": "fanart",
"side": null,
"filename": "fanart/2578-5.jpg",
"resolution": "1920x1080"
}
]
}
},
The key "2578"is dynamic and changes by the game. I have this succesfuly decoding with the following:
struct GameDBData : Decodable {
let data : GameDataImages
}
struct GameDataImages : Decodable {
let images : Images
}
struct Images : Decodable {
var innerArray: [String: [Inner]]
struct Inner: Decodable {
let id : Int
let fileName : String
let type : String
enum CodingKeys : String, CodingKey {
case id
case fileName = "filename"
case type
}
}
private struct CustomCodingKeys: CodingKey {
var stringValue : String
init?(stringValue : String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
self.innerArray = [String: [Inner]]()
for key in container.allKeys {
let value = try container.decode([Inner].self, forKey: CustomCodingKeys(stringValue: key.stringValue)!)
self.innerArray[key.stringValue] = value
}
}
}
Ive tried firstIndex(where:) but Im not getting anywhere with it. How can i iterate the 2578 array to get the filename for type: "clearlogo" and type: "fanart" im needing?
You have array of Inner.Something like this,
var array:[Inner] = [_yourData]
If we assume your array should have only one object with type "fanart" & "clearlogo" for each.we can get each filename using ,
let fanartFileName = array.filter({$0.type == "fanart"})[0].fileName
let clearlogoFileName = array.filter({$0.type == "clearlogo"})[0].fileName
some times your array havn't any record for these types.or have more than one,
let fanartArray = ar.filter({$0.type == "fanart"})
if fanartArray.count > 0 {
fanartArray.forEach { (record) in
print(record.fileName)
}
}

How do take the values from a large JSON response with multiple arrays and brackets?

I have a JSON response (over 500lines) , that has several arrays and brackets inside. Basically an overly nested response.
I am using Alamofire for fetching and i get a JSONData as response. But I don't know how to take values from the response and print it on a custom cell .
I am using Alamofire 5 to get the JSON response.
Please excuse if this sounds too newbieish , I am new to iOS developing.
{
"availabilityResultList": [
{
"availabilityRouteList": [
{
"availabilityByDateList": [
{
"originDestinationOptionList": [
{
"fareComponentGroupList": [
{
"boundList": [
{
"availFlightSegmentList": [
{
"addOnSegment": false,
"flightSegment": {
"journeyDuration": "PT1H15M",
"distance": 1,
"departureAirport": {
"locationName": "LOC",
"cityInfo": {
"country": {
"locationName": "LOC",
"currency": {
"code": "USD"
},
"locationCode": "LOC",
"locationNameLanguage": "EN"
},
"city": {
"locationName": "LOC",
"locationCode": "LOC",
"locationNameLanguage": "EN"
}
},
"codeContext": "ASD",
"language": "EN",
"locationCode": "LOC"
},
"equipment": {
"airEquipType": "ABC",
"changeofGauge": false
},
"remark": "NO",
"ticketType": "PP",
"onTimeRate": 0,
"flightNotes": [
{
"deiCode": 0
}
],
"flightSegmentID": "",
"flownMileageQty": 0,
"flightNumber": "106",
"groundDuration": "",
"trafficRestriction": {
"code": "",
"explanation": {}
},
"stopQuantity": 0,
"codeshare": false,
"secureFlightDataRequired": false,
"departureDateTime": {
"month": 5,
"hour": 6,
"year": 2019,
"timezone": 270,
"day": 6,
"minute": 45,
"second": 0
},
"ondControlled": false,
"arrivalDateTime": {
"month": 5,
"hour": 8,
"year": 2019,
"timezone": 270,
"day": 6,
"minute": 0,
"second": 0
},
"airline": {
"code": "RQ"
},
"arrivalAirport": {
"locationName": "LOC",
"cityInfo": {
"country": {
"locationName": "AA",
"currency": {
"code": "USD"
},
"locationCode": "AA",
"locationNameLanguage": "EN"
},
"city": {
"locationName": "loc",
"locationCode": "LOC",
"locationNameLanguage": "EN"
}
},
"codeContext": "ASD",
"language": "EN",
"locationCode": "LOC"
},
"sector": "ASD",
"accumulatedDuration": ""
},
"marriageGroup": "-1"
}
],
"boundCode": "Outbound"
}
]
}
]
},
JSON is a very simple format and easy to read. There are only two different collection types and four value types.
{} is a dictionary and is subscripted by key
[] is an array and is subscripted by index (zero-based)
Everything in double quotes is String, even "123" and "false"
false and true is Bool
Numeric values are Double, without fractional digits Int
<null> is NSNull
With SwiftyJSON you get distance in flightSegment with (root represents the top level object`)
root["availabilityResultList"][0]["availabilityRouteList"][0]["availabilityByDateList"][0]["originDestinationOptionList"][0]["fareComponentGroupList"][0]["boundList"][0]["availFlightSegmentList"][0]["flightSegment"]["distance"]

Resources